@servicetitan/dte-pdf-editor 1.92.1 → 1.92.2
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.
- package/dist/components/field-config-panel/field-sidebar.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-sidebar.js +8 -4
- package/dist/components/field-config-panel/field-sidebar.js.map +1 -1
- package/dist/components/field-sidebar/calculated-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/calculated-field-type-list.js +1 -1
- package/dist/components/field-sidebar/calculated-field-type-list.js.map +1 -1
- package/dist/components/field-sidebar/data-model-field-type-list.d.ts +15 -1
- package/dist/components/field-sidebar/data-model-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/data-model-field-type-list.js +54 -10
- package/dist/components/field-sidebar/data-model-field-type-list.js.map +1 -1
- package/dist/components/field-sidebar/e-sign-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/e-sign-field-type-list.js +1 -1
- package/dist/components/field-sidebar/e-sign-field-type-list.js.map +1 -1
- package/dist/components/field-sidebar/field-sidebar.d.ts +7 -0
- package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
- package/dist/components/field-sidebar/field-sidebar.js +8 -11
- package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
- package/dist/components/field-sidebar/field-type.d.ts +0 -2
- package/dist/components/field-sidebar/field-type.d.ts.map +1 -1
- package/dist/components/field-sidebar/field-type.js +2 -2
- package/dist/components/field-sidebar/field-type.js.map +1 -1
- package/dist/components/field-sidebar/fillable-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/fillable-field-type-list.js +1 -1
- package/dist/components/field-sidebar/fillable-field-type-list.js.map +1 -1
- package/dist/components/field-sidebar/form-fields-type-list.d.ts +28 -5
- package/dist/components/field-sidebar/form-fields-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/form-fields-type-list.js +27 -6
- package/dist/components/field-sidebar/form-fields-type-list.js.map +1 -1
- package/dist/components/field-sidebar/generic-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/generic-field-type-list.js +1 -1
- package/dist/components/field-sidebar/generic-field-type-list.js.map +1 -1
- package/dist/components/pdf-canvas/canvas-controls.d.ts +15 -9
- package/dist/components/pdf-canvas/canvas-controls.d.ts.map +1 -1
- package/dist/components/pdf-canvas/canvas-controls.js +14 -4
- package/dist/components/pdf-canvas/canvas-controls.js.map +1 -1
- package/dist/components/pdf-canvas/pdf-canvas.d.ts +2 -6
- package/dist/components/pdf-canvas/pdf-canvas.d.ts.map +1 -1
- package/dist/components/pdf-canvas/pdf-canvas.js +5 -11
- package/dist/components/pdf-canvas/pdf-canvas.js.map +1 -1
- package/dist/components/pdf-editor/pdf-editor.d.ts +0 -2
- package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
- package/dist/components/pdf-editor/pdf-editor.js +4 -3
- package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js +1 -4
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.js +2 -3
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +2 -4
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-e-sign.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-e-sign.js +2 -3
- package/dist/components/pdf-view/pdf-view-e-sign.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-field-container.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-field-container.js +3 -10
- package/dist/components/pdf-view/pdf-view-field-container.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-fillable.js +1 -1
- package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
- package/dist/components/pdf-view/pdf-view.d.ts +0 -5
- package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view.js +24 -51
- package/dist/components/pdf-view/pdf-view.js.map +1 -1
- package/dist/constants/field.constants.d.ts.map +1 -1
- package/dist/constants/field.constants.js +6 -27
- package/dist/constants/field.constants.js.map +1 -1
- package/dist/constants/menu-group.d.ts +5 -0
- package/dist/constants/menu-group.d.ts.map +1 -1
- package/dist/constants/menu-group.js +5 -2
- package/dist/constants/menu-group.js.map +1 -1
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +0 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useFormFields.d.ts +3 -1
- package/dist/hooks/useFormFields.d.ts.map +1 -1
- package/dist/hooks/useFormFields.js +20 -3
- package/dist/hooks/useFormFields.js.map +1 -1
- package/dist/hooks/useZoom.d.ts +0 -2
- package/dist/hooks/useZoom.d.ts.map +1 -1
- package/dist/hooks/useZoom.js +6 -18
- package/dist/hooks/useZoom.js.map +1 -1
- package/dist/interface/types.d.ts +0 -2
- package/dist/interface/types.d.ts.map +1 -1
- package/dist/interface/types.js.map +1 -1
- package/dist/styles/field-type.css +5 -0
- package/dist/styles/formula-modal.css +17 -0
- package/dist/styles/pdf-canvas.css +15 -91
- package/dist/styles/pdf-editor.css +0 -1
- package/dist/styles/pdf-field-overlay.css +78 -109
- package/dist/styles/variables.css +0 -3
- package/dist/utils/field/get-navigable-fields.utils.js +1 -1
- package/dist/utils/field/get-navigable-fields.utils.js.map +1 -1
- package/dist/utils/field/index.d.ts +0 -1
- package/dist/utils/field/index.d.ts.map +1 -1
- package/dist/utils/field/index.js +0 -1
- package/dist/utils/field/index.js.map +1 -1
- package/dist/utils/pdf/index.d.ts +0 -1
- package/dist/utils/pdf/index.d.ts.map +1 -1
- package/dist/utils/pdf/index.js +0 -1
- package/dist/utils/pdf/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/field-config-panel/field-sidebar.tsx +24 -8
- package/src/components/field-sidebar/calculated-field-type-list.tsx +0 -1
- package/src/components/field-sidebar/data-model-field-type-list.tsx +143 -29
- package/src/components/field-sidebar/e-sign-field-type-list.tsx +0 -1
- package/src/components/field-sidebar/field-sidebar.tsx +19 -18
- package/src/components/field-sidebar/field-type.tsx +3 -13
- package/src/components/field-sidebar/fillable-field-type-list.tsx +0 -1
- package/src/components/field-sidebar/form-fields-type-list.tsx +137 -95
- package/src/components/field-sidebar/generic-field-type-list.tsx +0 -1
- package/src/components/pdf-canvas/canvas-controls.tsx +128 -30
- package/src/components/pdf-canvas/pdf-canvas.tsx +8 -18
- package/src/components/pdf-editor/pdf-editor.tsx +5 -7
- package/src/components/pdf-fields-overlay/pdf-fields-overlay.tsx +1 -1
- package/src/components/pdf-fields-overlay/pdf-overlay-field-e-sign.tsx +6 -8
- package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +1 -17
- package/src/components/pdf-view/pdf-view-e-sign.tsx +6 -8
- package/src/components/pdf-view/pdf-view-field-container.tsx +1 -21
- package/src/components/pdf-view/pdf-view-fillable.tsx +1 -1
- package/src/components/pdf-view/pdf-view.tsx +95 -156
- package/src/constants/field.constants.ts +6 -28
- package/src/constants/menu-group.ts +5 -2
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useFormFields.ts +24 -2
- package/src/hooks/useZoom.ts +8 -20
- package/src/interface/types.ts +0 -3
- package/src/styles/field-type.css +5 -0
- package/src/styles/formula-modal.css +17 -0
- package/src/styles/pdf-canvas.css +15 -91
- package/src/styles/pdf-editor.css +0 -1
- package/src/styles/pdf-field-overlay.css +78 -109
- package/src/styles/variables.css +0 -3
- package/src/utils/field/get-navigable-fields.utils.ts +1 -1
- package/src/utils/field/index.ts +0 -1
- package/src/utils/pdf/index.ts +0 -1
- package/dist/hooks/usePinchZoom.d.ts +0 -14
- package/dist/hooks/usePinchZoom.d.ts.map +0 -1
- package/dist/hooks/usePinchZoom.js +0 -58
- package/dist/hooks/usePinchZoom.js.map +0 -1
- package/dist/utils/field/get-field-type-option.utils.d.ts +0 -3
- package/dist/utils/field/get-field-type-option.utils.d.ts.map +0 -1
- package/dist/utils/field/get-field-type-option.utils.js +0 -10
- package/dist/utils/field/get-field-type-option.utils.js.map +0 -1
- package/dist/utils/pdf/pinch-zoom.utils.d.ts +0 -6
- package/dist/utils/pdf/pinch-zoom.utils.d.ts.map +0 -1
- package/dist/utils/pdf/pinch-zoom.utils.js +0 -18
- package/dist/utils/pdf/pinch-zoom.utils.js.map +0 -1
- package/src/hooks/usePinchZoom.ts +0 -86
- package/src/utils/field/__tests__/get-field-type-option.utils.test.ts +0 -53
- package/src/utils/field/get-field-type-option.utils.ts +0 -16
- package/src/utils/pdf/__tests__/pinch-zoom.utils.test.ts +0 -58
- package/src/utils/pdf/pinch-zoom.utils.ts +0 -30
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Flex
|
|
2
|
-
import { FC, ReactNode,
|
|
1
|
+
import { Flex } from '@servicetitan/anvil2';
|
|
2
|
+
import { FC, ReactNode, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { BASE_PAGE_WIDTH } from '../../constants';
|
|
4
|
-
import {
|
|
4
|
+
import { useZoom } from '../../hooks';
|
|
5
5
|
import {
|
|
6
6
|
type DataChangePayload,
|
|
7
7
|
type DataModelValues,
|
|
@@ -15,11 +15,7 @@ import {
|
|
|
15
15
|
mapColorsToRecipients,
|
|
16
16
|
sortFieldsByReadingOrder,
|
|
17
17
|
} from '../../utils';
|
|
18
|
-
import {
|
|
19
|
-
FieldNavigationControls,
|
|
20
|
-
ZoomControls,
|
|
21
|
-
ZoomControlsProps,
|
|
22
|
-
} from '../pdf-canvas/canvas-controls';
|
|
18
|
+
import { CanvasControls } from '../pdf-canvas/canvas-controls';
|
|
23
19
|
import { PdfDocumentRenderer } from '../pdf-canvas/pdf-document-renderer';
|
|
24
20
|
import { PdfViewCalculated } from './pdf-view-calculated';
|
|
25
21
|
import { PdfViewDataModel } from './pdf-view-data-model';
|
|
@@ -34,10 +30,6 @@ export interface PdfViewProps {
|
|
|
34
30
|
viewMode?: PdfViewMode;
|
|
35
31
|
data?: DataModelValues;
|
|
36
32
|
zoomable?: boolean;
|
|
37
|
-
zoomProps?: {
|
|
38
|
-
vertical: ZoomControlsProps['vertical'];
|
|
39
|
-
position: ZoomControlsProps['position'];
|
|
40
|
-
};
|
|
41
33
|
enableInputNavigation?: boolean;
|
|
42
34
|
pdfUrl: string;
|
|
43
35
|
loading?: boolean;
|
|
@@ -82,23 +74,14 @@ export const PdfView: FC<PdfViewProps> = ({
|
|
|
82
74
|
pdfUrl,
|
|
83
75
|
recipients,
|
|
84
76
|
viewMode,
|
|
85
|
-
zoomProps,
|
|
86
77
|
zoomable = true,
|
|
87
78
|
}) => {
|
|
88
79
|
const [isPdfLoaded, setIsPdfLoaded] = useState<boolean>(false);
|
|
89
80
|
const [previewData, setPreviewData] = useState<DataModelValues>(data ?? {});
|
|
90
|
-
const { resetZoom,
|
|
81
|
+
const { resetZoom, zoom, zoomIn, zoomOut } = useZoom();
|
|
91
82
|
const activeData = viewMode === 'preview' ? previewData : data;
|
|
92
83
|
|
|
93
84
|
const pdfWrapperRef = useRef<HTMLDivElement>(null);
|
|
94
|
-
const navControlsRef = useRef<HTMLDivElement>(null);
|
|
95
|
-
const contentContainerRef = useRef<HTMLDivElement>(null);
|
|
96
|
-
usePinchZoom(contentContainerRef, {
|
|
97
|
-
enabled: zoomable,
|
|
98
|
-
setZoomValue,
|
|
99
|
-
zoom,
|
|
100
|
-
zoomByFactor,
|
|
101
|
-
});
|
|
102
85
|
|
|
103
86
|
const onDocumentLoadSuccess = (pageNum: number) => {
|
|
104
87
|
setIsPdfLoaded(true);
|
|
@@ -128,6 +111,7 @@ export const PdfView: FC<PdfViewProps> = ({
|
|
|
128
111
|
[navigableFields],
|
|
129
112
|
);
|
|
130
113
|
|
|
114
|
+
// -1 means no field has been navigated to yet, so the first "next" lands on index 0.
|
|
131
115
|
const [currentFieldIndex, setCurrentFieldIndex] = useState(-1);
|
|
132
116
|
|
|
133
117
|
const handleFieldOverlayFocus = (fieldId: string) => {
|
|
@@ -136,160 +120,115 @@ export const PdfView: FC<PdfViewProps> = ({
|
|
|
136
120
|
}
|
|
137
121
|
};
|
|
138
122
|
|
|
139
|
-
const getFieldElement = (fieldId: string) => {
|
|
140
|
-
const root = pdfWrapperRef.current?.getRootNode() as Document | ShadowRoot | undefined;
|
|
141
|
-
return root?.querySelector<HTMLElement>(`[id="dte-pdf-field-${fieldId}"]`) ?? undefined;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
useLayoutEffect(() => {
|
|
145
|
-
const field = navigableFields[currentFieldIndex];
|
|
146
|
-
const navControlsEl = navControlsRef.current;
|
|
147
|
-
if (!field || !navControlsEl) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const fieldEl = getFieldElement(field.id);
|
|
152
|
-
if (!fieldEl) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const containerTop = contentContainerRef.current?.getBoundingClientRect().top ?? 0;
|
|
157
|
-
navControlsEl.style.top = `${fieldEl.getBoundingClientRect().top - containerTop}px`;
|
|
158
|
-
}, [currentFieldIndex, navigableFields]);
|
|
159
|
-
|
|
160
123
|
const handleFocusField = (fieldId: string) => {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
124
|
+
const root = pdfWrapperRef.current?.getRootNode() as Document | ShadowRoot | undefined;
|
|
125
|
+
const fieldEl = root?.querySelector<HTMLElement>(`[id="dte-pdf-field-${fieldId}"]`);
|
|
126
|
+
fieldEl?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
127
|
+
const focusableElement = fieldEl?.querySelector<HTMLElement>('input, textarea') ?? fieldEl;
|
|
128
|
+
focusableElement?.focus();
|
|
167
129
|
};
|
|
168
130
|
|
|
169
131
|
const goToField = (index: number) => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
handleFocusField(field.id);
|
|
132
|
+
let i = index;
|
|
133
|
+
if (i < 0) {
|
|
134
|
+
i = 0;
|
|
135
|
+
} else if (i >= navigableFields.length) {
|
|
136
|
+
i = navigableFields.length - 1;
|
|
176
137
|
}
|
|
177
|
-
|
|
138
|
+
const field = navigableFields[i];
|
|
178
139
|
|
|
179
|
-
|
|
180
|
-
|
|
140
|
+
setCurrentFieldIndex(i);
|
|
141
|
+
handleFocusField(field.id);
|
|
181
142
|
};
|
|
182
143
|
|
|
183
144
|
const recipientsColors = mapColorsToRecipients(recipients);
|
|
184
|
-
const hasNavigableFields = navigableFields.length
|
|
185
|
-
const shouldShowFieldNavigation = enableInputNavigation && hasNavigableFields;
|
|
186
|
-
|
|
145
|
+
const hasNavigableFields = navigableFields.length === 0;
|
|
187
146
|
return (
|
|
188
147
|
<Flex
|
|
148
|
+
flex={1}
|
|
149
|
+
className="dte-pdf-editor-theme dte-pdf-editor-content-container --view-mode"
|
|
189
150
|
direction="column"
|
|
190
|
-
style={{ position: 'relative', minWidth: 0 }}
|
|
191
|
-
className="dte-pdf-editor-theme"
|
|
192
|
-
ref={contentContainerRef}
|
|
193
151
|
>
|
|
194
|
-
{isPdfLoaded &&
|
|
195
|
-
<
|
|
196
|
-
<FieldNavigationControls
|
|
197
|
-
isStarted={currentFieldIndex !== -1}
|
|
198
|
-
onNextField={handleNextField}
|
|
199
|
-
/>
|
|
200
|
-
</div>
|
|
201
|
-
)}
|
|
202
|
-
{isPdfLoaded && zoomable && (
|
|
203
|
-
<ZoomControls
|
|
204
|
-
{...zoomProps}
|
|
152
|
+
{isPdfLoaded && (
|
|
153
|
+
<CanvasControls
|
|
205
154
|
zoom={zoom}
|
|
206
155
|
onZoomIn={zoomIn}
|
|
207
156
|
onZoomOut={zoomOut}
|
|
157
|
+
zoomable={zoomable}
|
|
208
158
|
onResetZoom={resetZoom}
|
|
159
|
+
enableInputNavigation={enableInputNavigation}
|
|
160
|
+
disabledPrev={hasNavigableFields}
|
|
161
|
+
disabledNext={hasNavigableFields}
|
|
162
|
+
disabledOnFirstField={hasNavigableFields}
|
|
163
|
+
disabledOnLastField={hasNavigableFields}
|
|
164
|
+
onPrevField={() => goToField(currentFieldIndex - 1)}
|
|
165
|
+
onNextField={() => goToField(currentFieldIndex + 1)}
|
|
166
|
+
onFirstField={() => goToField(0)}
|
|
167
|
+
onLastField={() => goToField(navigableFields.length - 1)}
|
|
209
168
|
/>
|
|
210
169
|
)}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
gap={2}
|
|
216
|
-
className="dte-pdf-canvas-controls"
|
|
217
|
-
>
|
|
218
|
-
<Text style={{ color: '#ffffff' }}>Review and complete</Text>
|
|
219
|
-
<FieldNavigationControls
|
|
220
|
-
isStarted={currentFieldIndex !== -1}
|
|
221
|
-
onNextField={handleNextField}
|
|
222
|
-
/>
|
|
223
|
-
</Flex>
|
|
224
|
-
)}
|
|
225
|
-
<Flex
|
|
226
|
-
flex={1}
|
|
227
|
-
className="dte-pdf-editor-content-container --view-mode"
|
|
228
|
-
direction="column"
|
|
170
|
+
<div
|
|
171
|
+
ref={pdfWrapperRef}
|
|
172
|
+
className="dte-pdf-wrapper"
|
|
173
|
+
style={{ width: BASE_PAGE_WIDTH * zoom }}
|
|
229
174
|
>
|
|
230
|
-
<
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
</PdfViewFieldContainer>
|
|
288
|
-
))}
|
|
289
|
-
</div>
|
|
290
|
-
)}
|
|
291
|
-
</div>
|
|
292
|
-
</Flex>
|
|
175
|
+
<PdfDocumentRenderer
|
|
176
|
+
pdfUrl={pdfUrl}
|
|
177
|
+
loading={loading}
|
|
178
|
+
pageWidth={BASE_PAGE_WIDTH * zoom}
|
|
179
|
+
errorPlaceholder={errorPlaceholder}
|
|
180
|
+
loadingPlaceholder={loadingPlaceholder}
|
|
181
|
+
onDocumentLoadSuccess={onDocumentLoadSuccess}
|
|
182
|
+
/>
|
|
183
|
+
{isPdfLoaded && (
|
|
184
|
+
<div className="dte-pdf-field-overlay --view-mode">
|
|
185
|
+
{fields.map(field => (
|
|
186
|
+
<PdfViewFieldContainer
|
|
187
|
+
key={field.id}
|
|
188
|
+
field={field}
|
|
189
|
+
data={viewMode === 'preview' ? previewData : data}
|
|
190
|
+
error={errors[field.path!] || ''}
|
|
191
|
+
recipientsColors={recipientsColors}
|
|
192
|
+
pdfWrapperRef={pdfWrapperRef}
|
|
193
|
+
zoom={zoom}
|
|
194
|
+
onFocus={handleFieldOverlayFocus}
|
|
195
|
+
>
|
|
196
|
+
{(field.type === FieldTypeEnum.dataModel ||
|
|
197
|
+
field.type === FieldTypeEnum.forms) && (
|
|
198
|
+
<PdfViewDataModel
|
|
199
|
+
field={field}
|
|
200
|
+
data={viewMode === 'preview' ? previewData : data}
|
|
201
|
+
/>
|
|
202
|
+
)}
|
|
203
|
+
{field.type === FieldTypeEnum.eSign && (
|
|
204
|
+
<PdfViewESign field={field} />
|
|
205
|
+
)}
|
|
206
|
+
{field.type === FieldTypeEnum.calculated && (
|
|
207
|
+
<PdfViewCalculated
|
|
208
|
+
field={field}
|
|
209
|
+
data={viewMode === 'preview' ? previewData : data}
|
|
210
|
+
holidays={holidays}
|
|
211
|
+
viewMode={viewMode}
|
|
212
|
+
/>
|
|
213
|
+
)}
|
|
214
|
+
{field.type === FieldTypeEnum.fillable && (
|
|
215
|
+
<PdfViewFillable
|
|
216
|
+
data={viewMode === 'preview' ? previewData : data}
|
|
217
|
+
field={field}
|
|
218
|
+
readOnly={makeFillableFieldsReadonly}
|
|
219
|
+
fillingBy={fillingBy}
|
|
220
|
+
viewMode={viewMode}
|
|
221
|
+
onDataChange={handleDataChange}
|
|
222
|
+
/>
|
|
223
|
+
)}
|
|
224
|
+
{field.type === FieldTypeEnum.generic && (
|
|
225
|
+
<PdfViewGeneric field={field} />
|
|
226
|
+
)}
|
|
227
|
+
</PdfViewFieldContainer>
|
|
228
|
+
))}
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
293
232
|
</Flex>
|
|
294
233
|
);
|
|
295
234
|
};
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
import NumbersIcon from '@servicetitan/anvil2/assets/icons/material/round/123.svg';
|
|
2
|
-
import AbcIcon from '@servicetitan/anvil2/assets/icons/material/round/abc.svg';
|
|
3
|
-
import BadgeIcon from '@servicetitan/anvil2/assets/icons/material/round/badge.svg';
|
|
4
|
-
import CalculateIcon from '@servicetitan/anvil2/assets/icons/material/round/calculate.svg';
|
|
5
|
-
import CalendarMonthIcon from '@servicetitan/anvil2/assets/icons/material/round/calendar_month.svg';
|
|
6
|
-
import CheckboxIcon from '@servicetitan/anvil2/assets/icons/material/round/check_box.svg';
|
|
7
|
-
import EventAvailableIcon from '@servicetitan/anvil2/assets/icons/material/round/event_available.svg';
|
|
8
|
-
import GestureIcon from '@servicetitan/anvil2/assets/icons/material/round/gesture.svg';
|
|
9
|
-
import RadioIcon from '@servicetitan/anvil2/assets/icons/material/round/radio_button_checked.svg';
|
|
10
|
-
import ShortTextIcon from '@servicetitan/anvil2/assets/icons/material/round/short_text.svg';
|
|
11
|
-
import TableChartIcon from '@servicetitan/anvil2/assets/icons/material/round/table_chart.svg';
|
|
12
|
-
import TextIcon from '@servicetitan/anvil2/assets/icons/material/round/text_fields.svg';
|
|
13
|
-
import IconAppointmentsIcon from '@servicetitan/anvil2/assets/icons/st/appointments.svg';
|
|
14
|
-
|
|
15
1
|
import {
|
|
16
2
|
type CalculatedFormatForDate,
|
|
17
3
|
type CalculatedFormatForNumber,
|
|
@@ -58,11 +44,11 @@ export const GENERIC_FIELD_DEFAULT_SIZES = {
|
|
|
58
44
|
} as Record<PdfFieldSubType, { width: number; height: number }>;
|
|
59
45
|
|
|
60
46
|
export const FILLABLE_FIELD_TYPES: FieldTypeOption[] = [
|
|
61
|
-
{ label: 'Text Field', type: FieldTypeEnum.fillable, subType: 'text'
|
|
62
|
-
{ label: 'Number Field', type: FieldTypeEnum.fillable, subType: 'number'
|
|
63
|
-
{ label: 'Date Field', type: FieldTypeEnum.fillable, subType: 'date'
|
|
64
|
-
{ label: 'Checkbox', type: FieldTypeEnum.fillable, subType: 'checkbox'
|
|
65
|
-
{ label: 'Radio', type: FieldTypeEnum.fillable, subType: 'radio'
|
|
47
|
+
{ label: 'Text Field', type: FieldTypeEnum.fillable, subType: 'text' },
|
|
48
|
+
{ label: 'Number Field', type: FieldTypeEnum.fillable, subType: 'number' },
|
|
49
|
+
{ label: 'Date Field', type: FieldTypeEnum.fillable, subType: 'date' },
|
|
50
|
+
{ label: 'Checkbox', type: FieldTypeEnum.fillable, subType: 'checkbox' },
|
|
51
|
+
{ label: 'Radio', type: FieldTypeEnum.fillable, subType: 'radio' },
|
|
66
52
|
];
|
|
67
53
|
|
|
68
54
|
export const E_SIGN_FIELD_TYPES: FieldTypeOption[] = [
|
|
@@ -70,31 +56,26 @@ export const E_SIGN_FIELD_TYPES: FieldTypeOption[] = [
|
|
|
70
56
|
label: 'Signature',
|
|
71
57
|
type: FieldTypeEnum.eSign,
|
|
72
58
|
subType: ESignFieldType.signature,
|
|
73
|
-
icon: GestureIcon,
|
|
74
59
|
},
|
|
75
60
|
{
|
|
76
61
|
label: 'Initials',
|
|
77
62
|
type: FieldTypeEnum.eSign,
|
|
78
63
|
subType: ESignFieldType.initials,
|
|
79
|
-
icon: AbcIcon,
|
|
80
64
|
},
|
|
81
65
|
{
|
|
82
66
|
label: 'Date Signed',
|
|
83
67
|
type: FieldTypeEnum.eSign,
|
|
84
68
|
subType: ESignFieldType.dateSigned,
|
|
85
|
-
icon: EventAvailableIcon,
|
|
86
69
|
},
|
|
87
70
|
{
|
|
88
|
-
label: '
|
|
71
|
+
label: 'Date Signed Offset',
|
|
89
72
|
type: FieldTypeEnum.eSign,
|
|
90
73
|
subType: ESignFieldType.dateSignedOffset,
|
|
91
|
-
icon: IconAppointmentsIcon,
|
|
92
74
|
},
|
|
93
75
|
{
|
|
94
76
|
label: 'Full Name',
|
|
95
77
|
type: FieldTypeEnum.eSign,
|
|
96
78
|
subType: ESignFieldType.fullName,
|
|
97
|
-
icon: BadgeIcon,
|
|
98
79
|
},
|
|
99
80
|
];
|
|
100
81
|
|
|
@@ -102,7 +83,6 @@ export const CALCULATED_FIELD_TYPES: FieldTypeOption[] = [
|
|
|
102
83
|
{
|
|
103
84
|
label: 'Calculated Field',
|
|
104
85
|
type: FieldTypeEnum.calculated,
|
|
105
|
-
icon: CalculateIcon,
|
|
106
86
|
},
|
|
107
87
|
];
|
|
108
88
|
|
|
@@ -111,13 +91,11 @@ export const GENERIC_FIELD_TYPES: FieldTypeOption[] = [
|
|
|
111
91
|
label: 'Text',
|
|
112
92
|
type: FieldTypeEnum.generic,
|
|
113
93
|
subType: 'text',
|
|
114
|
-
icon: TextIcon,
|
|
115
94
|
},
|
|
116
95
|
{
|
|
117
96
|
label: 'Table',
|
|
118
97
|
type: FieldTypeEnum.generic,
|
|
119
98
|
subType: 'table',
|
|
120
|
-
icon: TableChartIcon,
|
|
121
99
|
},
|
|
122
100
|
];
|
|
123
101
|
|
|
@@ -1,6 +1,5 @@
|
|
|
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
|
-
import IconFactCheck from '@servicetitan/anvil2/assets/icons/material/round/fact_check.svg';
|
|
4
3
|
import IconPhotoSizeSelectSmall from '@servicetitan/anvil2/assets/icons/material/round/photo_size_select_small.svg';
|
|
5
4
|
import IconCommercial from '@servicetitan/anvil2/assets/icons/st/commercial.svg';
|
|
6
5
|
import IconEstimate from '@servicetitan/anvil2/assets/icons/st/estimate.svg';
|
|
@@ -12,6 +11,11 @@ export interface MenuGroupModel {
|
|
|
12
11
|
key: FieldTypeEnum;
|
|
13
12
|
}
|
|
14
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Sidebar tabs. Forms (`FieldTypeEnum.forms`) has no tab of its own: form fields are offered as a
|
|
16
|
+
* group inside Merge Tags, next to the data model groups. `hideFields: [FieldTypeEnum.forms]` still
|
|
17
|
+
* hides that group.
|
|
18
|
+
*/
|
|
15
19
|
export const menuGroups: MenuGroupModel[] = [
|
|
16
20
|
{ svgIcon: IconEstimate, label: 'Merge Tags', key: FieldTypeEnum.dataModel },
|
|
17
21
|
{ svgIcon: IconBorderColor, label: 'E-Sign', key: FieldTypeEnum.eSign },
|
|
@@ -30,5 +34,4 @@ export const menuGroups: MenuGroupModel[] = [
|
|
|
30
34
|
label: 'Generic Fields',
|
|
31
35
|
key: FieldTypeEnum.generic,
|
|
32
36
|
},
|
|
33
|
-
{ svgIcon: IconFactCheck, label: 'Forms', key: FieldTypeEnum.forms },
|
|
34
37
|
];
|
package/src/hooks/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ import { formFieldInfoToFieldTypeOption } from '../utils';
|
|
|
10
10
|
|
|
11
11
|
interface UseFormFieldsResult {
|
|
12
12
|
isLoading: boolean;
|
|
13
|
+
isLoadingForms: boolean;
|
|
14
|
+
formsWithFields: FormInfo[];
|
|
13
15
|
formFieldsByFormId: FormFieldsByFormIdI;
|
|
14
16
|
formulaLoadingFormId: null | number;
|
|
15
17
|
formFieldOptions: FieldTypeOption[];
|
|
@@ -18,12 +20,15 @@ interface UseFormFieldsResult {
|
|
|
18
20
|
requestFormFieldsById: (formId: number) => void;
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
const EMPTY_FORMS: FormInfo[] = [];
|
|
24
|
+
|
|
21
25
|
export const useFormFields = (
|
|
22
26
|
fields: PdfField[],
|
|
23
27
|
onFormSelect?: (
|
|
24
28
|
formIds: number[],
|
|
25
29
|
sendFormFields: (formFieldsByFormId: FormFieldsByFormIdI) => void,
|
|
26
30
|
) => void,
|
|
31
|
+
forms: FormInfo[] = EMPTY_FORMS,
|
|
27
32
|
): UseFormFieldsResult => {
|
|
28
33
|
const [formFieldsByFormId, setFormFieldsByFormId] = useState<FormFieldsByFormIdI>({});
|
|
29
34
|
const [formulaLoadingFormId, setFormulaLoadingFormId] = useState<number | null>(null);
|
|
@@ -40,7 +45,13 @@ export const useFormFields = (
|
|
|
40
45
|
return;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
/*
|
|
49
|
+
* Every form is asked for up front, not just the ones a formula already references: a form
|
|
50
|
+
* with no fields has nothing to offer and is dropped from the list (`formsWithFields`), and
|
|
51
|
+
* that cannot be decided without its fields. They go out as one batched call, and the reply
|
|
52
|
+
* is cached, so opening a form afterwards costs nothing.
|
|
53
|
+
*/
|
|
54
|
+
const initialFormIds = new Set<number>(forms.map(form => form.id));
|
|
44
55
|
fields.forEach(field => {
|
|
45
56
|
if (field.type === FieldTypeEnum.calculated) {
|
|
46
57
|
if (field?.formula?.tokens.length) {
|
|
@@ -90,7 +101,7 @@ export const useFormFields = (
|
|
|
90
101
|
cancelled = true;
|
|
91
102
|
cancelAnimationFrame(rafId);
|
|
92
103
|
};
|
|
93
|
-
}, [fields, onFormSelect]);
|
|
104
|
+
}, [fields, forms, onFormSelect]);
|
|
94
105
|
|
|
95
106
|
const formFieldOptions = useMemo(() => {
|
|
96
107
|
if (!selectedForm) {
|
|
@@ -143,10 +154,21 @@ export const useFormFields = (
|
|
|
143
154
|
[formFieldsByFormId, onFormSelect],
|
|
144
155
|
);
|
|
145
156
|
|
|
157
|
+
/*
|
|
158
|
+
* A form is offered only once it is known to have something to drag out of it. Until its reply
|
|
159
|
+
* lands it is neither listed nor counted as empty — `isLoadingForms` stands in for it.
|
|
160
|
+
*/
|
|
161
|
+
const formsWithFields = useMemo(() => {
|
|
162
|
+
return forms.filter(form => !!formFieldsByFormId[form.id]?.length);
|
|
163
|
+
}, [forms, formFieldsByFormId]);
|
|
164
|
+
|
|
146
165
|
const isLoading = selectedForm != null && formFieldsLoadingFormIds[selectedForm.id];
|
|
166
|
+
const isLoadingForms = forms.some(form => !!formFieldsLoadingFormIds[form.id]);
|
|
147
167
|
|
|
148
168
|
return {
|
|
149
169
|
isLoading,
|
|
170
|
+
isLoadingForms,
|
|
171
|
+
formsWithFields,
|
|
150
172
|
formFieldsByFormId,
|
|
151
173
|
formulaLoadingFormId,
|
|
152
174
|
formFieldOptions,
|
package/src/hooks/useZoom.ts
CHANGED
|
@@ -1,37 +1,25 @@
|
|
|
1
1
|
import { useCallback, useState } from 'react';
|
|
2
|
-
import { ZOOM_DEFAULT, ZOOM_STEP } from '../constants';
|
|
3
|
-
import { clampZoom } from '../utils';
|
|
2
|
+
import { ZOOM_DEFAULT, ZOOM_MAX, ZOOM_MIN, ZOOM_STEP } from '../constants';
|
|
4
3
|
|
|
5
4
|
export interface UseZoomReturn {
|
|
6
5
|
zoom: number;
|
|
7
6
|
zoomIn(): void;
|
|
8
7
|
zoomOut(): void;
|
|
9
8
|
resetZoom(): void;
|
|
10
|
-
setZoomValue(value: number): void;
|
|
11
|
-
zoomByFactor(factor: number): void;
|
|
12
9
|
}
|
|
13
10
|
|
|
11
|
+
const clampZoom = (value: number) =>
|
|
12
|
+
Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, Math.round(value * 100) / 100));
|
|
13
|
+
|
|
14
14
|
/**
|
|
15
15
|
* Manages a page zoom factor (1 = 100%), clamped to [ZOOM_MIN, ZOOM_MAX] in ZOOM_STEP increments.
|
|
16
16
|
*/
|
|
17
17
|
export const useZoom = (initialZoom: number = ZOOM_DEFAULT): UseZoomReturn => {
|
|
18
18
|
const [zoom, setZoom] = useState<number>(initialZoom);
|
|
19
19
|
|
|
20
|
-
const zoomIn = useCallback(() =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const zoomOut = useCallback(() => {
|
|
24
|
-
setZoom(prev => clampZoom(prev - ZOOM_STEP));
|
|
25
|
-
}, []);
|
|
26
|
-
const resetZoom = useCallback(() => {
|
|
27
|
-
setZoom(ZOOM_DEFAULT);
|
|
28
|
-
}, []);
|
|
29
|
-
const setZoomValue = useCallback((value: number) => {
|
|
30
|
-
setZoom(clampZoom(value));
|
|
31
|
-
}, []);
|
|
32
|
-
const zoomByFactor = useCallback((factor: number) => {
|
|
33
|
-
setZoom(prev => clampZoom(prev * factor));
|
|
34
|
-
}, []);
|
|
20
|
+
const zoomIn = useCallback(() => setZoom(prev => clampZoom(prev + ZOOM_STEP)), []);
|
|
21
|
+
const zoomOut = useCallback(() => setZoom(prev => clampZoom(prev - ZOOM_STEP)), []);
|
|
22
|
+
const resetZoom = useCallback(() => setZoom(ZOOM_DEFAULT), []);
|
|
35
23
|
|
|
36
|
-
return {
|
|
24
|
+
return { zoom, zoomIn, zoomOut, resetZoom };
|
|
37
25
|
};
|
package/src/interface/types.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { Svg } from '@servicetitan/anvil2';
|
|
2
|
-
|
|
3
1
|
export enum FieldTypeEnum {
|
|
4
2
|
dataModel = 'dataModel',
|
|
5
3
|
forms = 'forms',
|
|
@@ -166,7 +164,6 @@ export interface FieldTypeOption {
|
|
|
166
164
|
fieldType?: SchemaFieldType;
|
|
167
165
|
formSnapshot?: FormulaFieldFormSnapshot;
|
|
168
166
|
sampleData?: unknown;
|
|
169
|
-
icon?: Svg;
|
|
170
167
|
}
|
|
171
168
|
|
|
172
169
|
export interface DataModelFieldGroup {
|
|
@@ -69,6 +69,18 @@
|
|
|
69
69
|
gap: 0;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/*
|
|
73
|
+
* The panels share one scroller, so the list — always the taller of the two — would otherwise keep
|
|
74
|
+
* driving the scroll height while the fields panel is shown, leaving the fields followed by a
|
|
75
|
+
* screenful of nothing. Both bounds are needed: the min-content floor above keeps a max-height on
|
|
76
|
+
* its own from taking effect.
|
|
77
|
+
*/
|
|
78
|
+
.dte-formula-sidebar-slide-track.--detail .dte-formula-sidebar-slide-panel:not(.--form-fields) {
|
|
79
|
+
min-height: 0;
|
|
80
|
+
max-height: 0;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
.dte-formula-sidebar-forms-heading {
|
|
73
85
|
flex-shrink: 0;
|
|
74
86
|
}
|
|
@@ -94,6 +106,11 @@
|
|
|
94
106
|
white-space: nowrap;
|
|
95
107
|
}
|
|
96
108
|
|
|
109
|
+
.dte-formula-field-list-item.--skeleton {
|
|
110
|
+
cursor: default;
|
|
111
|
+
pointer-events: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
97
114
|
.dte-formula-field-list-item:hover,
|
|
98
115
|
.dte-formula-field-list-item.--highlight {
|
|
99
116
|
color: var(--color-neutral-100, #e8e8e8);
|