@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.
- package/README.md +25 -1
- package/dist/components/display-conditions/condition-row.d.ts.map +1 -1
- package/dist/components/display-conditions/condition-row.js +5 -1
- package/dist/components/display-conditions/condition-row.js.map +1 -1
- package/dist/components/display-conditions/condition-value-control.d.ts +14 -2
- package/dist/components/display-conditions/condition-value-control.d.ts.map +1 -1
- package/dist/components/display-conditions/condition-value-control.js +84 -13
- package/dist/components/display-conditions/condition-value-control.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js +2 -2
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-data-model.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-data-model.js +2 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-data-model.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 +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js +14 -7
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.js +9 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.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 +5 -11
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-calculated.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-calculated.js +2 -2
- package/dist/components/pdf-view/pdf-view-calculated.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-data-model.js +2 -2
- package/dist/components/pdf-view/pdf-view-data-model.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 +1 -1
- 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 +5 -15
- package/dist/components/pdf-view/pdf-view-field-container.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-fillable.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-fillable.js +22 -33
- package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-generic.js +1 -1
- package/dist/components/pdf-view/pdf-view-generic.js.map +1 -1
- package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view.js +1 -1
- package/dist/components/pdf-view/pdf-view.js.map +1 -1
- package/dist/constants/field.constants.d.ts +3 -0
- package/dist/constants/field.constants.d.ts.map +1 -1
- package/dist/constants/field.constants.js +3 -0
- package/dist/constants/field.constants.js.map +1 -1
- package/dist/hooks/useLookupOptions.d.ts +24 -8
- package/dist/hooks/useLookupOptions.d.ts.map +1 -1
- package/dist/hooks/useLookupOptions.js +47 -9
- package/dist/hooks/useLookupOptions.js.map +1 -1
- package/dist/interface/host-events.d.ts +27 -10
- package/dist/interface/host-events.d.ts.map +1 -1
- package/dist/interface/host-events.js +10 -10
- package/dist/interface/host-events.js.map +1 -1
- package/dist/interface/types.d.ts +8 -0
- package/dist/interface/types.d.ts.map +1 -1
- package/dist/interface/types.js.map +1 -1
- package/dist/utils/field/get-field-bounds-style.utils.d.ts +20 -0
- package/dist/utils/field/get-field-bounds-style.utils.d.ts.map +1 -0
- package/dist/utils/field/get-field-bounds-style.utils.js +53 -0
- package/dist/utils/field/get-field-bounds-style.utils.js.map +1 -0
- package/dist/utils/field/get-field-text-clip.utils.d.ts +13 -0
- package/dist/utils/field/get-field-text-clip.utils.d.ts.map +1 -0
- package/dist/utils/field/get-field-text-clip.utils.js +25 -0
- package/dist/utils/field/get-field-text-clip.utils.js.map +1 -0
- package/dist/utils/field/get-field-typography.utils.d.ts +10 -0
- package/dist/utils/field/get-field-typography.utils.d.ts.map +1 -0
- package/dist/utils/field/get-field-typography.utils.js +20 -0
- package/dist/utils/field/get-field-typography.utils.js.map +1 -0
- package/dist/utils/field/index.d.ts +3 -0
- package/dist/utils/field/index.d.ts.map +1 -1
- package/dist/utils/field/index.js +3 -0
- package/dist/utils/field/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/display-conditions/condition-row.tsx +8 -1
- package/src/components/display-conditions/condition-value-control.tsx +126 -39
- package/src/components/pdf-fields-overlay/pdf-overlay-field-calculated.tsx +5 -2
- package/src/components/pdf-fields-overlay/pdf-overlay-field-data-model.tsx +2 -1
- package/src/components/pdf-fields-overlay/pdf-overlay-field-e-sign.tsx +3 -1
- package/src/components/pdf-fields-overlay/pdf-overlay-field-fillable.tsx +28 -8
- package/src/components/pdf-fields-overlay/pdf-overlay-field-generic.tsx +9 -1
- package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +9 -13
- package/src/components/pdf-view/pdf-view-calculated.tsx +9 -2
- package/src/components/pdf-view/pdf-view-data-model.tsx +2 -2
- package/src/components/pdf-view/pdf-view-e-sign.tsx +3 -4
- package/src/components/pdf-view/pdf-view-field-container.tsx +6 -16
- package/src/components/pdf-view/pdf-view-fillable.tsx +29 -28
- package/src/components/pdf-view/pdf-view-generic.tsx +1 -1
- package/src/components/pdf-view/pdf-view.tsx +4 -1
- package/src/constants/field.constants.ts +5 -0
- package/src/hooks/useLookupOptions.ts +76 -14
- package/src/interface/host-events.ts +27 -15
- package/src/interface/types.ts +8 -0
- package/src/styles/inline-editable.css +1 -0
- package/src/styles/pdf-field-overlay.css +80 -27
- package/src/utils/field/__tests__/get-field-bounds-style.utils.test.ts +50 -0
- package/src/utils/field/__tests__/get-field-text-clip.utils.test.ts +43 -0
- package/src/utils/field/__tests__/get-field-typography.utils.test.ts +56 -0
- package/src/utils/field/get-field-bounds-style.utils.ts +65 -0
- package/src/utils/field/get-field-text-clip.utils.ts +31 -0
- package/src/utils/field/get-field-typography.utils.ts +23 -0
- 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"
|
|
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
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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={{
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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 {
|
|
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
|
|
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=
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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 {
|
|
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: '
|
|
25
|
-
height: '
|
|
26
|
-
|
|
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:
|
|
110
|
-
height:
|
|
119
|
+
width: field.width,
|
|
120
|
+
height: field.height,
|
|
111
121
|
margin: 0,
|
|
112
|
-
|
|
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: '
|
|
133
|
-
height: '
|
|
143
|
+
width: '100%',
|
|
144
|
+
height: '100%',
|
|
134
145
|
}}
|
|
135
146
|
>
|
|
136
147
|
<input
|
|
137
148
|
style={{
|
|
138
|
-
width:
|
|
139
|
-
height:
|
|
149
|
+
width: field.width,
|
|
150
|
+
height: field.height,
|
|
140
151
|
margin: 0,
|
|
141
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
187
|
-
|
|
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
|
|
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`
|
|
12
|
-
* - `loading`
|
|
13
|
-
* - `
|
|
14
|
-
* - `
|
|
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' | '
|
|
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
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
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
|
-
|
|
42
|
-
if (!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('
|
|
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
|
-
|
|
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
|
-
/**
|
|
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)}`;
|
package/src/interface/types.ts
CHANGED
|
@@ -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 {
|