@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.
- package/README.md +0 -2
- package/dist/components/field-config-panel/dropdown-field-items.d.ts +9 -0
- package/dist/components/field-config-panel/dropdown-field-items.d.ts.map +1 -0
- package/dist/components/field-config-panel/dropdown-field-items.js +60 -0
- package/dist/components/field-config-panel/dropdown-field-items.js.map +1 -0
- package/dist/components/field-config-panel/field-config-panel-overlay.d.ts +1 -1
- package/dist/components/field-config-panel/field-config-panel-overlay.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-config-panel.d.ts +1 -1
- package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-config-panel.js +5 -1
- package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
- package/dist/components/field-config-panel/fillable-default-config.d.ts +19 -0
- package/dist/components/field-config-panel/fillable-default-config.d.ts.map +1 -0
- package/dist/components/field-config-panel/fillable-default-config.js +77 -0
- package/dist/components/field-config-panel/fillable-default-config.js.map +1 -0
- package/dist/components/pdf-editor/pdf-editor.js +1 -1
- package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js +1 -1
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.d.ts +9 -0
- 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 +39 -12
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.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 +4 -4
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.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 +7 -20
- 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 +6 -2
- package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
- package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view.js +9 -2
- package/dist/components/pdf-view/pdf-view.js.map +1 -1
- package/dist/constants/field.constants.d.ts +1 -0
- package/dist/constants/field.constants.d.ts.map +1 -1
- package/dist/constants/field.constants.js +9 -0
- package/dist/constants/field.constants.js.map +1 -1
- package/dist/hooks/usePdfFieldDnD.d.ts.map +1 -1
- package/dist/hooks/usePdfFieldDnD.js +6 -0
- package/dist/hooks/usePdfFieldDnD.js.map +1 -1
- package/dist/hooks/usePdfFieldSelection.d.ts +1 -1
- package/dist/hooks/usePdfFieldSelection.d.ts.map +1 -1
- package/dist/hooks/usePdfFieldSelection.js +3 -1
- package/dist/hooks/usePdfFieldSelection.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interface/types.d.ts +46 -1
- package/dist/interface/types.d.ts.map +1 -1
- package/dist/interface/types.js.map +1 -1
- package/dist/styles/index.css +1 -0
- package/dist/styles/inline-editable.css +2 -0
- package/dist/styles/pdf-canvas.css +2 -1
- package/dist/styles/pdf-field-overlay.css +2 -111
- package/dist/styles/pdf-field.css +111 -0
- package/dist/styles/variables.css +1 -0
- package/dist/utils/data-model/index.d.ts +1 -0
- package/dist/utils/data-model/index.d.ts.map +1 -1
- package/dist/utils/data-model/index.js +1 -0
- package/dist/utils/data-model/index.js.map +1 -1
- package/dist/utils/data-model/resolve-fields-default-values.d.ts +3 -0
- package/dist/utils/data-model/resolve-fields-default-values.d.ts.map +1 -0
- package/dist/utils/data-model/resolve-fields-default-values.js +19 -0
- package/dist/utils/data-model/resolve-fields-default-values.js.map +1 -0
- package/dist/utils/exposes-utils.d.ts +2 -0
- package/dist/utils/exposes-utils.d.ts.map +1 -0
- package/dist/utils/exposes-utils.js +2 -0
- package/dist/utils/exposes-utils.js.map +1 -0
- package/dist/utils/field/field-background-color.utils.d.ts +1 -1
- package/dist/utils/field/field-background-color.utils.d.ts.map +1 -1
- package/dist/utils/field/field-background-color.utils.js +1 -1
- package/dist/utils/field/field-background-color.utils.js.map +1 -1
- package/dist/utils/field/get-dropdown-field-items.utils.d.ts +3 -0
- package/dist/utils/field/get-dropdown-field-items.utils.d.ts.map +1 -0
- package/dist/utils/field/get-dropdown-field-items.utils.js +7 -0
- package/dist/utils/field/get-dropdown-field-items.utils.js.map +1 -0
- package/dist/utils/field/get-field-bounds-style.utils.d.ts +1 -2
- package/dist/utils/field/get-field-bounds-style.utils.d.ts.map +1 -1
- package/dist/utils/field/get-field-bounds-style.utils.js +9 -4
- package/dist/utils/field/get-field-bounds-style.utils.js.map +1 -1
- package/dist/utils/field/get-fillable-default-value.utils.d.ts +29 -0
- package/dist/utils/field/get-fillable-default-value.utils.d.ts.map +1 -0
- package/dist/utils/field/get-fillable-default-value.utils.js +59 -0
- package/dist/utils/field/get-fillable-default-value.utils.js.map +1 -0
- package/dist/utils/field/index.d.ts +2 -0
- package/dist/utils/field/index.d.ts.map +1 -1
- package/dist/utils/field/index.js +2 -0
- package/dist/utils/field/index.js.map +1 -1
- package/dist/utils/recipients/map-colors.js +23 -23
- package/dist/utils/recipients/map-colors.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/field-types/e-sign-fields.test.ts +3 -3
- package/src/__tests__/field-types/fillable-fields.test.ts +142 -6
- package/src/components/field-config-panel/dropdown-field-items.tsx +138 -0
- package/src/components/field-config-panel/field-config-panel-overlay.tsx +1 -1
- package/src/components/field-config-panel/field-config-panel.tsx +16 -1
- package/src/components/field-config-panel/fillable-default-config.tsx +155 -0
- package/src/components/pdf-editor/pdf-editor.tsx +1 -1
- package/src/components/pdf-fields-overlay/pdf-fields-overlay.tsx +1 -1
- package/src/components/pdf-fields-overlay/pdf-overlay-field-fillable.tsx +66 -3
- package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +6 -3
- package/src/components/pdf-view/pdf-view-field-container.tsx +21 -41
- package/src/components/pdf-view/pdf-view-fillable.tsx +25 -2
- package/src/components/pdf-view/pdf-view.tsx +11 -3
- package/src/constants/field.constants.ts +10 -0
- package/src/hooks/usePdfFieldDnD.ts +6 -0
- package/src/hooks/usePdfFieldSelection.ts +8 -2
- package/src/index.ts +1 -0
- package/src/interface/types.ts +36 -1
- package/src/styles/index.css +1 -0
- package/src/styles/inline-editable.css +2 -0
- package/src/styles/pdf-canvas.css +2 -1
- package/src/styles/pdf-field-overlay.css +2 -111
- package/src/styles/pdf-field.css +111 -0
- package/src/styles/variables.css +1 -0
- package/src/utils/data-model/index.ts +1 -0
- package/src/utils/data-model/resolve-fields-default-values.ts +19 -0
- package/src/utils/exposes-utils.ts +1 -0
- package/src/utils/field/__tests__/field-background-color.utils.test.ts +3 -3
- package/src/utils/field/__tests__/get-field-bounds-style.utils.test.ts +0 -1
- package/src/utils/field/__tests__/get-fillable-default-value.utils.test.ts +137 -0
- package/src/utils/field/field-background-color.utils.ts +1 -1
- package/src/utils/field/get-dropdown-field-items.utils.ts +14 -0
- package/src/utils/field/get-field-bounds-style.utils.ts +11 -11
- package/src/utils/field/get-fillable-default-value.utils.ts +70 -0
- package/src/utils/field/index.ts +2 -0
- package/src/utils/recipients/map-colors.ts +23 -23
|
@@ -11,70 +11,15 @@
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
/* Editor-only chrome. Layout metrics come from getFieldBoundsStyle. */
|
|
15
|
-
.dte-pdf-field-editor {
|
|
16
|
-
outline: 1px dashed #999;
|
|
17
|
-
outline-offset: 0;
|
|
18
|
-
border: none;
|
|
19
|
-
pointer-events: auto;
|
|
20
|
-
border-radius: 4px;
|
|
21
|
-
|
|
22
|
-
&.--edit-mode {
|
|
23
|
-
overflow: unset !important;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
&.--selected {
|
|
27
|
-
outline: 2px solid var(--border-color-active);
|
|
28
|
-
cursor: move;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
&.--unselected {
|
|
32
|
-
cursor: pointer;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
&.--dragging {
|
|
36
|
-
opacity: 0.5;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
&.--colored:not(.--selected) {
|
|
40
|
-
outline-color: var(--border-color-active) !important;
|
|
41
|
-
box-shadow: 0 0 2px 0 var(--border-color-active);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
&.--error {
|
|
45
|
-
outline-color: var(--danger) !important;
|
|
46
|
-
box-shadow: 0 0 4px 0 var(--danger);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
14
|
.dte-pdf-field-error-message {
|
|
51
15
|
top: 100%;
|
|
52
16
|
min-width: 100px;
|
|
53
17
|
position: absolute;
|
|
54
|
-
margin-top: var(--spacing-
|
|
18
|
+
margin-top: var(--spacing-1, 8px);
|
|
55
19
|
color: var(--danger);
|
|
56
20
|
font-size: var(--typescale-small, 10px);
|
|
57
21
|
}
|
|
58
22
|
|
|
59
|
-
.dte-pdf-field-label {
|
|
60
|
-
position: absolute;
|
|
61
|
-
display: flex;
|
|
62
|
-
align-items: center;
|
|
63
|
-
gap: var(--spacing-half, 4px);
|
|
64
|
-
background-color: var(--color-neutral-0, #ffffff);
|
|
65
|
-
border-radius: 4px;
|
|
66
|
-
padding: 1px var(--spacing-half, 4px);
|
|
67
|
-
top: -10px;
|
|
68
|
-
left: 3px;
|
|
69
|
-
font-size: var(--typescale-small, 10px);
|
|
70
|
-
line-height: 1;
|
|
71
|
-
white-space: nowrap;
|
|
72
|
-
color: var(--menu-active-color, #2270ee);
|
|
73
|
-
border-width: 1px;
|
|
74
|
-
border-style: solid;
|
|
75
|
-
border-color: inherit;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
23
|
.dte-pdf-field-resize-handle {
|
|
79
24
|
position: absolute;
|
|
80
25
|
bottom: 0;
|
|
@@ -91,59 +36,14 @@
|
|
|
91
36
|
}
|
|
92
37
|
}
|
|
93
38
|
|
|
94
|
-
.dte-add-field-button {
|
|
95
|
-
display: none;
|
|
96
|
-
position: absolute;
|
|
97
|
-
bottom: -20px;
|
|
98
|
-
left: -10px;
|
|
99
|
-
margin-left: 50%;
|
|
100
|
-
padding: var(--spacing-0, 0);
|
|
101
|
-
width: 20px;
|
|
102
|
-
height: 20px;
|
|
103
|
-
|
|
104
|
-
.dte-pdf-field-editor:hover & {
|
|
105
|
-
display: block;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.dte-pdf-field-view {
|
|
110
|
-
border: none;
|
|
111
|
-
outline: none;
|
|
112
|
-
cursor: default;
|
|
113
|
-
pointer-events: auto;
|
|
114
|
-
|
|
115
|
-
&.--fillable,
|
|
116
|
-
&.--eSign {
|
|
117
|
-
border-style: dashed;
|
|
118
|
-
border-width: 1px;
|
|
119
|
-
border-radius: 4px;
|
|
120
|
-
|
|
121
|
-
&:focus {
|
|
122
|
-
outline: 2px solid var(--border-color-active);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
39
|
.dte-pdf-field-content {
|
|
128
40
|
width: 100%;
|
|
129
41
|
min-width: 0;
|
|
130
42
|
min-height: 0;
|
|
131
43
|
overflow: hidden;
|
|
132
44
|
line-height: 1;
|
|
133
|
-
|
|
134
|
-
input[type='checkbox']:focus,
|
|
135
|
-
input[type='checkbox']:active,
|
|
136
|
-
input[type='radio']:active,
|
|
137
|
-
input[type='radio']:focus {
|
|
138
|
-
box-shadow:
|
|
139
|
-
0 0 0 2px var(--border-color-active, #2270ee) inset,
|
|
140
|
-
0 0 0 3px var(--color-neutral-0, #ffffff) inset;
|
|
141
|
-
}
|
|
142
45
|
}
|
|
143
46
|
|
|
144
|
-
/*
|
|
145
|
-
* Printed PDF text modes (no ellipsis — hard clip like PdfSharp).
|
|
146
|
-
*/
|
|
147
47
|
.dte-pdf-field-text--single-line {
|
|
148
48
|
width: 100%;
|
|
149
49
|
min-width: 0;
|
|
@@ -201,16 +101,7 @@
|
|
|
201
101
|
}
|
|
202
102
|
|
|
203
103
|
&:focus {
|
|
204
|
-
outline:
|
|
205
|
-
outline-offset: 2px;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.dte-pdf-field-editor & {
|
|
209
|
-
pointer-events: none;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
.dte-pdf-field-view & {
|
|
213
|
-
pointer-events: auto;
|
|
104
|
+
outline: none;
|
|
214
105
|
}
|
|
215
106
|
}
|
|
216
107
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
.dte-pdf-field-container {
|
|
2
|
+
--pdf-field-color: var(--color-blue-500, #0265dc);
|
|
3
|
+
--pdf-field-color-dark: var(--color-blue-500, #0265dc);
|
|
4
|
+
--pdf-field-opacity-color: #3d95ff1f;
|
|
5
|
+
|
|
6
|
+
pointer-events: auto;
|
|
7
|
+
border-radius: 4px;
|
|
8
|
+
|
|
9
|
+
&.--edit {
|
|
10
|
+
color: var(--pdf-field-color);
|
|
11
|
+
background-color: var(--pdf-field-opacity-color);
|
|
12
|
+
outline: 1px dashed var(--pdf-field-color);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
&.--view {
|
|
16
|
+
&.--fillable,
|
|
17
|
+
&.--eSign {
|
|
18
|
+
color: var(--pdf-field-color);
|
|
19
|
+
background-color: var(--pdf-field-opacity-color);
|
|
20
|
+
outline: 1px dashed var(--pdf-field-color);
|
|
21
|
+
|
|
22
|
+
&:focus-within {
|
|
23
|
+
box-shadow:
|
|
24
|
+
0 0 0 3px white,
|
|
25
|
+
0 0 0 5px var(--pdf-field-color-dark) !important;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
input[type='checkbox'],
|
|
29
|
+
select {
|
|
30
|
+
outline: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&:has(input:disabled),
|
|
34
|
+
&:has(select:disabled) {
|
|
35
|
+
--pdf-field-color: #707070 !important;
|
|
36
|
+
--pdf-field-color-dark: #707070 !important;
|
|
37
|
+
--pdf-field-opacity-color: #7070701f !important;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.dte-pdf-field-label {
|
|
43
|
+
position: absolute;
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: var(--spacing-half, 4px);
|
|
47
|
+
background-color: var(--color-neutral-0, #ffffff);
|
|
48
|
+
border-radius: 4px;
|
|
49
|
+
padding: 1px var(--spacing-half, 4px);
|
|
50
|
+
top: -10px;
|
|
51
|
+
left: 3px;
|
|
52
|
+
font-size: var(--typescale-small, 10px);
|
|
53
|
+
line-height: 1;
|
|
54
|
+
white-space: nowrap;
|
|
55
|
+
color: var(--pdf-field-color-dark);
|
|
56
|
+
border-width: 1px;
|
|
57
|
+
border-style: solid;
|
|
58
|
+
border-color: inherit;
|
|
59
|
+
font-weight: var(--font-weight-bold, 700);
|
|
60
|
+
|
|
61
|
+
span {
|
|
62
|
+
font-weight: var(--font-weight-regular, 400);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
input::placeholder,
|
|
67
|
+
input[type='date'],
|
|
68
|
+
input[type='date']::-webkit-calendar-picker-indicator {
|
|
69
|
+
color: var(--pdf-field-color);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
&.--selected {
|
|
73
|
+
box-shadow:
|
|
74
|
+
0 0 0 3px white,
|
|
75
|
+
0 0 0 5px var(--pdf-field-color-dark);
|
|
76
|
+
cursor: move;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&.--colored:not(.--selected) {
|
|
80
|
+
box-shadow: 0 0 3px 1px #616e7b;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
&.--unselected {
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&.--dragging {
|
|
88
|
+
opacity: 0.5;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
&.--error {
|
|
92
|
+
--pdf-field-color: var(--danger, #dc3545) !important;
|
|
93
|
+
--pdf-field-color-dark: var(--red, #0265dc) !important;
|
|
94
|
+
--pdf-field-opacity-color: var(--danger-opacity, #dc35451f) !important;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.dte-add-field-button {
|
|
98
|
+
display: none;
|
|
99
|
+
position: absolute;
|
|
100
|
+
bottom: -20px;
|
|
101
|
+
left: -10px;
|
|
102
|
+
margin-left: 50%;
|
|
103
|
+
padding: var(--spacing-0, 0);
|
|
104
|
+
width: 20px;
|
|
105
|
+
height: 20px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
&:hover .dte-add-field-button {
|
|
109
|
+
display: block;
|
|
110
|
+
}
|
|
111
|
+
}
|
package/src/styles/variables.css
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DataModelValues, PdfField } from '../../interface/types';
|
|
2
|
+
import { getTodayDateInputValue } from '../field';
|
|
3
|
+
|
|
4
|
+
export const resolveFieldsDefaultValues = (fields: PdfField[]): DataModelValues => {
|
|
5
|
+
const defaultValues: Record<string, any> = {};
|
|
6
|
+
for (const field of fields) {
|
|
7
|
+
if (field.default !== undefined) {
|
|
8
|
+
if (field.default.type === 'radio') {
|
|
9
|
+
defaultValues[field.path!] = field.id;
|
|
10
|
+
} else if (field.default.type === 'today') {
|
|
11
|
+
defaultValues[field.path!] = getTodayDateInputValue();
|
|
12
|
+
} else {
|
|
13
|
+
defaultValues[field.path!] = field.default.value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return defaultValues;
|
|
19
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './data-model/resolve-fields-default-values';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getFieldColor } from '../field-background-color.utils';
|
|
2
2
|
|
|
3
|
-
describe('
|
|
3
|
+
describe('getFieldColor', () => {
|
|
4
4
|
const subject = (recipient: string | undefined, recipientsColors: Record<string, string>) =>
|
|
5
|
-
|
|
5
|
+
getFieldColor(recipient, recipientsColors);
|
|
6
6
|
|
|
7
7
|
test('returns color for recipient when present in map', () => {
|
|
8
8
|
expect(subject('signer1', { signer1: '#ff0000', signer2: '#00ff00' })).toBe('#ff0000');
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { FieldTypeEnum, type FillableDefault, type PdfField } from '../../../interface/types';
|
|
2
|
+
import {
|
|
3
|
+
getFillableEffectiveValue,
|
|
4
|
+
getTodayDateInputValue,
|
|
5
|
+
isRadioDefaultSelected,
|
|
6
|
+
isRadioOptionChecked,
|
|
7
|
+
resolveFillableDefaultValue,
|
|
8
|
+
} from '../get-fillable-default-value.utils';
|
|
9
|
+
|
|
10
|
+
const buildField = (overrides: Partial<PdfField> = {}): PdfField => ({
|
|
11
|
+
id: 'field-1',
|
|
12
|
+
type: FieldTypeEnum.fillable,
|
|
13
|
+
subType: 'text',
|
|
14
|
+
x: 0,
|
|
15
|
+
y: 0,
|
|
16
|
+
page: 1,
|
|
17
|
+
label: 'Field Label',
|
|
18
|
+
width: 100,
|
|
19
|
+
height: 25,
|
|
20
|
+
...overrides,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('getTodayDateInputValue', () => {
|
|
24
|
+
test('matches the local calendar date in YYYY-MM-DD form', () => {
|
|
25
|
+
const now = new Date();
|
|
26
|
+
const expected = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(
|
|
27
|
+
now.getDate(),
|
|
28
|
+
).padStart(2, '0')}`;
|
|
29
|
+
|
|
30
|
+
expect(getTodayDateInputValue()).toBe(expected);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('resolveFillableDefaultValue', () => {
|
|
35
|
+
test('returns undefined when there is no default (pre-existing fields)', () => {
|
|
36
|
+
expect(resolveFillableDefaultValue(undefined)).toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('resolves a text default', () => {
|
|
40
|
+
expect(resolveFillableDefaultValue({ type: 'text', value: 'hello' })).toBe('hello');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('resolves a number default', () => {
|
|
44
|
+
expect(resolveFillableDefaultValue({ type: 'number', value: 42 })).toBe(42);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('resolves a checkbox default', () => {
|
|
48
|
+
expect(resolveFillableDefaultValue({ type: 'checkbox', value: true })).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('resolves a fixed date default as-is', () => {
|
|
52
|
+
expect(resolveFillableDefaultValue({ type: 'date', value: '2026-03-15' })).toBe('2026-03-15');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('resolves the today sentinel to the current local date, not a frozen date', () => {
|
|
56
|
+
expect(resolveFillableDefaultValue({ type: 'today' })).toBe(getTodayDateInputValue());
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('resolves radio to undefined - selection lives on the option field, not a scalar', () => {
|
|
60
|
+
expect(resolveFillableDefaultValue({ type: 'radio' })).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('getFillableEffectiveValue', () => {
|
|
65
|
+
const field = buildField({ default: { type: 'text', value: 'default text' } });
|
|
66
|
+
|
|
67
|
+
test('with a configured default and no stored value, returns the default', () => {
|
|
68
|
+
expect(getFillableEffectiveValue(field, undefined)).toBe('default text');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('without a configured default and no stored value, returns undefined (starts blank)', () => {
|
|
72
|
+
expect(getFillableEffectiveValue(buildField(), undefined)).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('an explicit stored value overrides the default', () => {
|
|
76
|
+
expect(getFillableEffectiveValue(field, 'typed by recipient')).toBe('typed by recipient');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('an explicit empty string is a real stored answer and is not re-defaulted', () => {
|
|
80
|
+
expect(getFillableEffectiveValue(field, '')).toBe('');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('an explicit false is a real stored answer and is not re-defaulted', () => {
|
|
84
|
+
const checkboxField = buildField({
|
|
85
|
+
subType: 'checkbox',
|
|
86
|
+
default: { type: 'checkbox', value: true },
|
|
87
|
+
});
|
|
88
|
+
expect(getFillableEffectiveValue(checkboxField, false)).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('an explicit 0 is a real stored answer and is not re-defaulted', () => {
|
|
92
|
+
const numberField = buildField({ subType: 'number', default: { type: 'number', value: 100 } });
|
|
93
|
+
expect(getFillableEffectiveValue(numberField, 0)).toBe(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('a date field defaults to today, resolved fresh rather than frozen at authoring time', () => {
|
|
97
|
+
const dateField = buildField({ subType: 'date', default: { type: 'today' } });
|
|
98
|
+
expect(getFillableEffectiveValue(dateField, undefined)).toBe(getTodayDateInputValue());
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('isRadioDefaultSelected', () => {
|
|
103
|
+
test('true when this option field carries the radio default flag', () => {
|
|
104
|
+
expect(isRadioDefaultSelected(buildField({ subType: 'radio', default: { type: 'radio' } }))).toBe(
|
|
105
|
+
true,
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('false when this option has no default', () => {
|
|
110
|
+
expect(isRadioDefaultSelected(buildField({ subType: 'radio' }))).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('false for a mismatched default type (defensive: should never happen post-validation)', () => {
|
|
114
|
+
const mismatched = { type: 'text', value: 'x' } as unknown as FillableDefault;
|
|
115
|
+
expect(isRadioDefaultSelected(buildField({ subType: 'radio', default: mismatched }))).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('isRadioOptionChecked', () => {
|
|
120
|
+
const defaultOption = buildField({ id: 'opt-yes', subType: 'radio', default: { type: 'radio' } });
|
|
121
|
+
const otherOption = buildField({ id: 'opt-no', subType: 'radio' });
|
|
122
|
+
|
|
123
|
+
test('the flagged option checks itself when the group has no stored answer yet', () => {
|
|
124
|
+
expect(isRadioOptionChecked(defaultOption, undefined)).toBe(true);
|
|
125
|
+
expect(isRadioOptionChecked(otherOption, undefined)).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('a stored selection of a different option overrides the default flag', () => {
|
|
129
|
+
// The recipient picked "opt-no" - the group's stored value is now that option's id.
|
|
130
|
+
expect(isRadioOptionChecked(defaultOption, 'opt-no')).toBe(false);
|
|
131
|
+
expect(isRadioOptionChecked(otherOption, 'opt-no')).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('a stored selection confirming the defaulted option still checks it, not by the default flag', () => {
|
|
135
|
+
expect(isRadioOptionChecked(defaultOption, 'opt-yes')).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FillableDropdownItem, type PdfField } from '../../interface/types';
|
|
2
|
+
|
|
3
|
+
export const getDropdownFieldItems = (
|
|
4
|
+
field: Pick<PdfField, 'dropdownItems'>,
|
|
5
|
+
): FillableDropdownItem[] => {
|
|
6
|
+
if (!Array.isArray(field.dropdownItems)) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return field.dropdownItems.filter(
|
|
11
|
+
(item): item is FillableDropdownItem =>
|
|
12
|
+
!!item && typeof item.value === 'string' && typeof item.label === 'string',
|
|
13
|
+
);
|
|
14
|
+
};
|
|
@@ -4,8 +4,7 @@ import { getFieldTypographyStyle } from './get-field-typography.utils';
|
|
|
4
4
|
|
|
5
5
|
interface FieldBoundsStyleOptions {
|
|
6
6
|
pageOffset?: { left: number; top: number };
|
|
7
|
-
|
|
8
|
-
borderColor?: string;
|
|
7
|
+
color?: string;
|
|
9
8
|
/**
|
|
10
9
|
* Current page zoom factor (1 = 100%). Field `x/y/width/height` are stored
|
|
11
10
|
* relative to the unzoomed reference page width, so they must be scaled by
|
|
@@ -46,12 +45,15 @@ export const getFieldBoundsStyle = (
|
|
|
46
45
|
field: PdfField,
|
|
47
46
|
options?: FieldBoundsStyleOptions,
|
|
48
47
|
): CSSProperties => {
|
|
49
|
-
const {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
const { color, pageOffset = { left: 0, top: 0 }, zoom = 1 } = options ?? {};
|
|
49
|
+
|
|
50
|
+
const colors = color
|
|
51
|
+
? {
|
|
52
|
+
'--pdf-field-color': color,
|
|
53
|
+
'--pdf-field-color-dark': color,
|
|
54
|
+
'--pdf-field-opacity-color': `${color}1f`,
|
|
55
|
+
}
|
|
56
|
+
: {};
|
|
55
57
|
|
|
56
58
|
return {
|
|
57
59
|
position: 'absolute',
|
|
@@ -67,11 +69,9 @@ export const getFieldBoundsStyle = (
|
|
|
67
69
|
margin: 0,
|
|
68
70
|
minWidth: 0,
|
|
69
71
|
minHeight: 0,
|
|
70
|
-
overflow: 'hidden',
|
|
71
72
|
lineHeight: 1,
|
|
72
73
|
...getFieldTypeSpecificStyle(field),
|
|
73
74
|
...getFieldTypographyStyle(field),
|
|
74
|
-
...
|
|
75
|
-
...(borderColor ? { borderColor } : {}),
|
|
75
|
+
...colors,
|
|
76
76
|
};
|
|
77
77
|
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type FillableDefault, type PdfField } from '../../interface/types';
|
|
2
|
+
|
|
3
|
+
/** Today's calendar date in the browser's own local timezone, as `YYYY-MM-DD`. */
|
|
4
|
+
export const getTodayDateInputValue = (): string => {
|
|
5
|
+
const today = new Date();
|
|
6
|
+
const month = String(today.getMonth() + 1).padStart(2, '0');
|
|
7
|
+
const day = String(today.getDate()).padStart(2, '0');
|
|
8
|
+
return `${today.getFullYear()}-${month}-${day}`;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolves a `FillableDefault` to the scalar it should display. `today` resolves dynamically to
|
|
13
|
+
* the current local date every time this is called - it is never frozen at authoring time.
|
|
14
|
+
* `radio` has no scalar of its own (selection is decided per sibling option field, not a value
|
|
15
|
+
* here) and resolves to `undefined`, same as no default at all.
|
|
16
|
+
*/
|
|
17
|
+
export const resolveFillableDefaultValue = (
|
|
18
|
+
fillableDefault: FillableDefault | undefined,
|
|
19
|
+
): string | number | boolean | undefined => {
|
|
20
|
+
switch (fillableDefault?.type) {
|
|
21
|
+
case 'text':
|
|
22
|
+
case 'number':
|
|
23
|
+
case 'checkbox':
|
|
24
|
+
case 'date':
|
|
25
|
+
case 'dropdown':
|
|
26
|
+
return fillableDefault.value;
|
|
27
|
+
case 'today':
|
|
28
|
+
return getTodayDateInputValue();
|
|
29
|
+
default:
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Merges a stored value with the field's configured default for *display* only: a stored value -
|
|
36
|
+
* including an explicit empty string, `false`, or `0` - always wins, since those are real answers
|
|
37
|
+
* the recipient already gave. Only a path that is genuinely absent from the data object (`storedValue
|
|
38
|
+
* === undefined`) falls back to the default. `??` already has exactly this semantic (it only
|
|
39
|
+
* falls through on `null`/`undefined`), so this is intentionally not a truthiness check.
|
|
40
|
+
*/
|
|
41
|
+
export const getFillableEffectiveValue = (
|
|
42
|
+
field: Pick<PdfField, 'default'>,
|
|
43
|
+
/*
|
|
44
|
+
* matches DataModelValues (Record<string, any>): a stored answer's runtime type varies by
|
|
45
|
+
* fillable subtype (string/number/boolean), same looseness callers already had via
|
|
46
|
+
* `data?.[field.path]`.
|
|
47
|
+
*/
|
|
48
|
+
storedValue: any,
|
|
49
|
+
): any => {
|
|
50
|
+
return storedValue ?? resolveFillableDefaultValue(field.default);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Whether this specific radio option field is the group's pre-selected default. */
|
|
54
|
+
export const isRadioDefaultSelected = (field: Pick<PdfField, 'default'>): boolean => {
|
|
55
|
+
return field.default?.type === 'radio';
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether this radio option should render checked: a stored selection for the group (any option's
|
|
60
|
+
* id, resolved by the caller) always wins, even if it picked a *different* option than the
|
|
61
|
+
* default - that is what "override" means for a radio group. Only when the group's path is
|
|
62
|
+
* genuinely unanswered yet (`resolvedValue === undefined`) does this option's own default flag
|
|
63
|
+
* decide it.
|
|
64
|
+
*/
|
|
65
|
+
export const isRadioOptionChecked = (
|
|
66
|
+
field: Pick<PdfField, 'id' | 'default'>,
|
|
67
|
+
resolvedValue: unknown,
|
|
68
|
+
): boolean => {
|
|
69
|
+
return resolvedValue !== undefined ? resolvedValue === field.id : isRadioDefaultSelected(field);
|
|
70
|
+
};
|
package/src/utils/field/index.ts
CHANGED
|
@@ -11,3 +11,5 @@ export * from './get-field-text-clip.utils';
|
|
|
11
11
|
export * from './get-field-type-option.utils';
|
|
12
12
|
export * from './get-field-typography.utils';
|
|
13
13
|
export * from './get-navigable-fields.utils';
|
|
14
|
+
export * from './get-dropdown-field-items.utils';
|
|
15
|
+
export * from './get-fillable-default-value.utils';
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import { type RecipientInfo } from '../../interface/types';
|
|
2
2
|
|
|
3
3
|
const colors = [
|
|
4
|
-
'#
|
|
5
|
-
'#
|
|
6
|
-
'#
|
|
7
|
-
'#
|
|
8
|
-
'#
|
|
9
|
-
'#
|
|
10
|
-
'#
|
|
11
|
-
'#
|
|
12
|
-
'#
|
|
13
|
-
'#
|
|
14
|
-
'#
|
|
15
|
-
'#
|
|
16
|
-
'#
|
|
17
|
-
'#
|
|
18
|
-
'#
|
|
19
|
-
'#
|
|
20
|
-
'#
|
|
21
|
-
'#
|
|
22
|
-
'#
|
|
23
|
-
'#
|
|
24
|
-
'#
|
|
25
|
-
'#
|
|
26
|
-
'#
|
|
4
|
+
'#1F77B4',
|
|
5
|
+
'#FF7F0E',
|
|
6
|
+
'#2CA02C',
|
|
7
|
+
'#9467BD',
|
|
8
|
+
'#8C564B',
|
|
9
|
+
'#E377C2',
|
|
10
|
+
'#7F7F7F',
|
|
11
|
+
'#BCBD22',
|
|
12
|
+
'#17BECF',
|
|
13
|
+
'#D62728',
|
|
14
|
+
'#98DF8A',
|
|
15
|
+
'#FFBB78',
|
|
16
|
+
'#C5B0D5',
|
|
17
|
+
'#C49C94',
|
|
18
|
+
'#F7B6D2',
|
|
19
|
+
'#C7C7C7',
|
|
20
|
+
'#DBDB8D',
|
|
21
|
+
'#9EDAE5',
|
|
22
|
+
'#E5E5E5',
|
|
23
|
+
'#393B79',
|
|
24
|
+
'#5254A3',
|
|
25
|
+
'#6B6EBD',
|
|
26
|
+
'#9C9EDA',
|
|
27
27
|
];
|
|
28
28
|
|
|
29
29
|
export const mapColorsToRecipients = (recipients?: RecipientInfo[]): Record<string, string> => {
|