@pdfme/ui 2.0.2 → 2.2.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 +7 -3
- package/dist/index.js +1 -1
- package/dist/index.js.LICENSE.txt +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/class.d.ts +2 -0
- package/dist/types/components/Designer/index.d.ts +4 -0
- package/dist/types/components/Paper.d.ts +1 -1
- package/dist/types/components/Schemas/SchemaUI.d.ts +1 -0
- package/dist/types/components/Schemas/TextSchema.d.ts +2 -0
- package/dist/types/helper.d.ts +2 -0
- package/package.json +2 -2
- package/src/components/Designer/Main/index.tsx +14 -27
- package/src/components/Designer/Sidebar/DetailView/TextPropEditor.tsx +61 -25
- package/src/components/Paper.tsx +2 -2
- package/src/components/Preview.tsx +2 -1
- package/src/components/Schemas/BarcodeSchema.tsx +2 -1
- package/src/components/Schemas/ImageSchema.tsx +2 -1
- package/src/components/Schemas/SchemaUI.tsx +1 -0
- package/src/components/Schemas/TextSchema.tsx +116 -27
- package/src/helper.ts +1 -1
package/dist/types/class.d.ts
CHANGED
@@ -25,6 +25,7 @@ export declare abstract class BaseUIClass {
|
|
25
25
|
};
|
26
26
|
rotate?: number | undefined;
|
27
27
|
alignment?: "center" | "left" | "right" | undefined;
|
28
|
+
verticalAlignment?: "top" | "bottom" | "middle" | undefined;
|
28
29
|
fontSize?: number | undefined;
|
29
30
|
fontName?: string | undefined;
|
30
31
|
fontColor?: string | undefined;
|
@@ -34,6 +35,7 @@ export declare abstract class BaseUIClass {
|
|
34
35
|
dynamicFontSize?: {
|
35
36
|
max: number;
|
36
37
|
min: number;
|
38
|
+
fit?: string | undefined;
|
37
39
|
} | undefined;
|
38
40
|
} | {
|
39
41
|
type: "image";
|
@@ -11,6 +11,7 @@ declare const TemplateEditor: ({ template, size, onSaveTemplate, onChangeTemplat
|
|
11
11
|
};
|
12
12
|
rotate?: number | undefined;
|
13
13
|
alignment?: "center" | "left" | "right" | undefined;
|
14
|
+
verticalAlignment?: "top" | "bottom" | "middle" | undefined;
|
14
15
|
fontSize?: number | undefined;
|
15
16
|
fontName?: string | undefined;
|
16
17
|
fontColor?: string | undefined;
|
@@ -20,6 +21,7 @@ declare const TemplateEditor: ({ template, size, onSaveTemplate, onChangeTemplat
|
|
20
21
|
dynamicFontSize?: {
|
21
22
|
max: number;
|
22
23
|
min: number;
|
24
|
+
fit?: string | undefined;
|
23
25
|
} | undefined;
|
24
26
|
} | {
|
25
27
|
type: "image";
|
@@ -59,6 +61,7 @@ declare const TemplateEditor: ({ template, size, onSaveTemplate, onChangeTemplat
|
|
59
61
|
};
|
60
62
|
rotate?: number | undefined;
|
61
63
|
alignment?: "center" | "left" | "right" | undefined;
|
64
|
+
verticalAlignment?: "top" | "bottom" | "middle" | undefined;
|
62
65
|
fontSize?: number | undefined;
|
63
66
|
fontName?: string | undefined;
|
64
67
|
fontColor?: string | undefined;
|
@@ -68,6 +71,7 @@ declare const TemplateEditor: ({ template, size, onSaveTemplate, onChangeTemplat
|
|
68
71
|
dynamicFontSize?: {
|
69
72
|
max: number;
|
70
73
|
min: number;
|
74
|
+
fit?: string | undefined;
|
71
75
|
} | undefined;
|
72
76
|
} | {
|
73
77
|
type: "image";
|
@@ -11,6 +11,7 @@ declare const _default: React.ForwardRefExoticComponent<SchemaUIProps & {
|
|
11
11
|
};
|
12
12
|
rotate?: number | undefined;
|
13
13
|
alignment?: "center" | "left" | "right" | undefined;
|
14
|
+
verticalAlignment?: "top" | "bottom" | "middle" | undefined;
|
14
15
|
fontSize?: number | undefined;
|
15
16
|
fontName?: string | undefined;
|
16
17
|
fontColor?: string | undefined;
|
@@ -20,6 +21,7 @@ declare const _default: React.ForwardRefExoticComponent<SchemaUIProps & {
|
|
20
21
|
dynamicFontSize?: {
|
21
22
|
max: number;
|
22
23
|
min: number;
|
24
|
+
fit?: string | undefined;
|
23
25
|
} | undefined;
|
24
26
|
};
|
25
27
|
} & React.RefAttributes<HTMLTextAreaElement>>;
|
package/dist/types/helper.d.ts
CHANGED
@@ -42,12 +42,14 @@ export declare const templateSchemas2SchemasList: (_template: Template) => Promi
|
|
42
42
|
lineHeight?: number | undefined;
|
43
43
|
rotate?: number | undefined;
|
44
44
|
alignment?: "center" | "left" | "right" | undefined;
|
45
|
+
verticalAlignment?: "top" | "bottom" | "middle" | undefined;
|
45
46
|
fontName?: string | undefined;
|
46
47
|
fontColor?: string | undefined;
|
47
48
|
characterSpacing?: number | undefined;
|
48
49
|
dynamicFontSize?: {
|
49
50
|
max: number;
|
50
51
|
min: number;
|
52
|
+
fit?: string | undefined;
|
51
53
|
} | undefined;
|
52
54
|
} | {
|
53
55
|
type: "image";
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@pdfme/ui",
|
3
|
-
"version": "2.0
|
3
|
+
"version": "2.2.0",
|
4
4
|
"sideEffects": false,
|
5
5
|
"author": "hand-dot",
|
6
6
|
"license": "MIT",
|
@@ -64,7 +64,7 @@
|
|
64
64
|
"webpack-cli": "^5.0.1"
|
65
65
|
},
|
66
66
|
"peerDependencies": {
|
67
|
-
"@pdfme/common": "^2.
|
67
|
+
"@pdfme/common": "^2.1.0"
|
68
68
|
},
|
69
69
|
"jest": {
|
70
70
|
"setupFiles": [
|
@@ -39,6 +39,7 @@ const DeleteButton = ({ activeElements: aes }: { activeElements: HTMLElement[] }
|
|
39
39
|
zIndex: 1,
|
40
40
|
top,
|
41
41
|
left,
|
42
|
+
padding: 2,
|
42
43
|
height: 24,
|
43
44
|
width: 24,
|
44
45
|
cursor: 'pointer',
|
@@ -52,7 +53,7 @@ const DeleteButton = ({ activeElements: aes }: { activeElements: HTMLElement[] }
|
|
52
53
|
justifyContent: 'center',
|
53
54
|
}}
|
54
55
|
>
|
55
|
-
<XMarkIcon width={
|
56
|
+
<XMarkIcon style={{ pointerEvents: 'none' }} width={24} height={24} />
|
56
57
|
</button>
|
57
58
|
);
|
58
59
|
};
|
@@ -195,14 +196,6 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
195
196
|
changeSchemas(flatten(arg));
|
196
197
|
};
|
197
198
|
|
198
|
-
const currentlyEditingThisTextSchema = (target: EventTarget | null) => {
|
199
|
-
if (!target) return false;
|
200
|
-
if (target instanceof HTMLTextAreaElement) {
|
201
|
-
return activeElements.map((ae) => ae.id).includes(target.parentElement?.id || '');
|
202
|
-
}
|
203
|
-
return false;
|
204
|
-
};
|
205
|
-
|
206
199
|
const onResize = ({ target, width, height, direction }: OnResize) => {
|
207
200
|
if (!target) return;
|
208
201
|
const s = target.style;
|
@@ -239,20 +232,7 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
239
232
|
};
|
240
233
|
|
241
234
|
return (
|
242
|
-
<div
|
243
|
-
ref={ref}
|
244
|
-
onClick={(e) => {
|
245
|
-
e.stopPropagation();
|
246
|
-
if (!currentlyEditingThisTextSchema(e.target)) {
|
247
|
-
setEditing(false);
|
248
|
-
}
|
249
|
-
// For MacOS CMD+SHIFT+3/4 screenshots where the keydown event is never received, check mouse too
|
250
|
-
if (!e.shiftKey) {
|
251
|
-
setIsPressShiftKey(false);
|
252
|
-
}
|
253
|
-
}}
|
254
|
-
style={{ overflow: 'overlay' }}
|
255
|
-
>
|
235
|
+
<div ref={ref} style={{ overflow: 'overlay' }}>
|
256
236
|
<Selecto
|
257
237
|
container={paperRefs.current[pageCursor]}
|
258
238
|
continueSelect={isPressShiftKey}
|
@@ -266,8 +246,7 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
266
246
|
if (paperRefs.current[pageCursor] === inputEvent.target) {
|
267
247
|
onEdit([]);
|
268
248
|
}
|
269
|
-
|
270
|
-
if (inputEvent.target.id === DELETE_BTN_ID) {
|
249
|
+
if (inputEvent.target?.id === DELETE_BTN_ID) {
|
271
250
|
removeSchemas(activeElements.map((ae) => ae.id));
|
272
251
|
}
|
273
252
|
}}
|
@@ -280,8 +259,15 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
280
259
|
if (!isClick && removed.length > 0) {
|
281
260
|
newActiveElements = activeElements.filter((ae) => !removed.includes(ae));
|
282
261
|
}
|
283
|
-
|
284
262
|
onEdit(newActiveElements);
|
263
|
+
|
264
|
+
if (newActiveElements != activeElements) {
|
265
|
+
setEditing(false);
|
266
|
+
}
|
267
|
+
// For MacOS CMD+SHIFT+3/4 screenshots where the keydown event is never received, check mouse too
|
268
|
+
if (!inputEvent.shiftKey) {
|
269
|
+
setIsPressShiftKey(false);
|
270
|
+
}
|
285
271
|
}}
|
286
272
|
/>
|
287
273
|
<Paper
|
@@ -338,9 +324,10 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
338
324
|
schema={schema}
|
339
325
|
onChangeHoveringSchemaId={onChangeHoveringSchemaId}
|
340
326
|
editable={editing && activeElements.map((ae) => ae.id).includes(schema.id)}
|
341
|
-
onChange={
|
327
|
+
onChange={(value) => {
|
342
328
|
changeSchemas([{ key: 'data', value, schemaId: schema.id }]);
|
343
329
|
}}
|
330
|
+
onStopEditing={() => setEditing(false)}
|
344
331
|
outline={hoveringSchemaId === schema.id ? '1px solid #18a0fb' : '1px dashed #4af'}
|
345
332
|
ref={inputRef}
|
346
333
|
/>
|
@@ -6,6 +6,13 @@ import {
|
|
6
6
|
DEFAULT_LINE_HEIGHT,
|
7
7
|
DEFAULT_CHARACTER_SPACING,
|
8
8
|
DEFAULT_FONT_COLOR,
|
9
|
+
VERTICAL_ALIGN_TOP,
|
10
|
+
VERTICAL_ALIGN_MIDDLE,
|
11
|
+
VERTICAL_ALIGN_BOTTOM,
|
12
|
+
DEFAULT_VERTICAL_ALIGNMENT,
|
13
|
+
DYNAMIC_FIT_VERTICAL,
|
14
|
+
DYNAMIC_FIT_HORIZONTAL,
|
15
|
+
DEFAULT_DYNAMIC_FIT,
|
9
16
|
} from '@pdfme/common';
|
10
17
|
import { FontContext } from '../../../../contexts';
|
11
18
|
import { SidebarProps } from '..';
|
@@ -24,11 +31,14 @@ const NumberInputSet = (props: {
|
|
24
31
|
width: string;
|
25
32
|
label: string;
|
26
33
|
value: number;
|
34
|
+
step?: number;
|
27
35
|
minNumber?: number;
|
28
36
|
maxNumber?: number;
|
37
|
+
disabled?: boolean;
|
38
|
+
style?: object;
|
29
39
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
30
40
|
}) => {
|
31
|
-
const { label, value, width, minNumber, maxNumber, onChange } = props;
|
41
|
+
const { label, step, value, width, minNumber, maxNumber, disabled, style, onChange } = props;
|
32
42
|
const formattedLabel = label.replace(/\s/g, '');
|
33
43
|
|
34
44
|
return (
|
@@ -37,10 +47,12 @@ const NumberInputSet = (props: {
|
|
37
47
|
<input
|
38
48
|
id={`input-${formattedLabel}`}
|
39
49
|
name={`input-${formattedLabel}`}
|
40
|
-
style={inputStyle}
|
50
|
+
style={{ ...inputStyle, ...style }}
|
41
51
|
onChange={onChange}
|
42
|
-
value={value}
|
52
|
+
value={isNaN(value) ? '' : value}
|
43
53
|
type="number"
|
54
|
+
step={step ?? 1}
|
55
|
+
disabled={disabled}
|
44
56
|
{...(minNumber && { min: minNumber })}
|
45
57
|
{...(maxNumber && { max: maxNumber })}
|
46
58
|
/>
|
@@ -92,13 +104,14 @@ const SelectSet = (props: {
|
|
92
104
|
label: string;
|
93
105
|
value: string;
|
94
106
|
options: string[];
|
107
|
+
width?: string;
|
95
108
|
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
96
109
|
}) => {
|
97
|
-
const { label, value, options, onChange } = props;
|
110
|
+
const { label, value, options, width, onChange } = props;
|
98
111
|
const formattedLabel = label.replace(/\s/g, '');
|
99
112
|
|
100
113
|
return (
|
101
|
-
<div style={{ width: '45%' }}>
|
114
|
+
<div style={{ width: width ?? '45%' }}>
|
102
115
|
<label htmlFor={`select-${formattedLabel}`}>{label}:</label>
|
103
116
|
<select
|
104
117
|
id={`select-${formattedLabel}`}
|
@@ -145,6 +158,8 @@ const TextPropEditor = (
|
|
145
158
|
) => {
|
146
159
|
const { changeSchemas, activeSchema } = props;
|
147
160
|
const alignments = ['left', 'center', 'right'];
|
161
|
+
const verticalAlignments = [VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_MIDDLE, VERTICAL_ALIGN_BOTTOM];
|
162
|
+
const dynamicFits = [DYNAMIC_FIT_HORIZONTAL, DYNAMIC_FIT_VERTICAL];
|
148
163
|
const font = useContext(FontContext);
|
149
164
|
const fallbackFontName = getFallbackFontName(font);
|
150
165
|
|
@@ -170,13 +185,22 @@ const TextPropEditor = (
|
|
170
185
|
/>
|
171
186
|
|
172
187
|
<SelectSet
|
173
|
-
label={'
|
188
|
+
label={'Horizontal Align'}
|
174
189
|
value={activeSchema.alignment ?? 'left'}
|
175
190
|
options={alignments}
|
176
191
|
onChange={(e) =>
|
177
192
|
changeSchemas([{ key: 'alignment', value: e.target.value, schemaId: activeSchema.id }])
|
178
193
|
}
|
179
194
|
/>
|
195
|
+
|
196
|
+
<SelectSet
|
197
|
+
label={'Vertical Align'}
|
198
|
+
value={activeSchema.verticalAlignment ?? DEFAULT_VERTICAL_ALIGNMENT}
|
199
|
+
options={verticalAlignments}
|
200
|
+
onChange={(e) => {
|
201
|
+
changeSchemas([{ key: 'verticalAlignment', value: e.target.value, schemaId: activeSchema.id }]);
|
202
|
+
}}
|
203
|
+
/>
|
180
204
|
</div>
|
181
205
|
<div
|
182
206
|
style={{
|
@@ -189,26 +213,17 @@ const TextPropEditor = (
|
|
189
213
|
<NumberInputSet
|
190
214
|
width="30%"
|
191
215
|
label={'FontSize(pt)'}
|
192
|
-
value={activeSchema.fontSize ?? DEFAULT_FONT_SIZE}
|
216
|
+
value={activeSchema.dynamicFontSize ? NaN : (activeSchema.fontSize ?? DEFAULT_FONT_SIZE)}
|
217
|
+
style={activeSchema.dynamicFontSize ? { background: '#ccc', cursor: 'not-allowed' } : {}}
|
218
|
+
disabled={!!activeSchema.dynamicFontSize}
|
193
219
|
onChange={(e) => {
|
194
|
-
|
195
|
-
const dynamincFontSizeMinAdjust = activeSchema.dynamicFontSize && activeSchema.dynamicFontSize.min > currentFontSize;
|
196
|
-
|
197
|
-
changeSchemas([
|
198
|
-
{ key: 'fontSize', value: currentFontSize, schemaId: activeSchema.id },
|
199
|
-
...(dynamincFontSizeMinAdjust
|
200
|
-
? [{
|
201
|
-
key: 'dynamicFontSize.min',
|
202
|
-
value: currentFontSize,
|
203
|
-
schemaId: activeSchema.id,
|
204
|
-
}]
|
205
|
-
: []),
|
206
|
-
]);
|
220
|
+
changeSchemas([{ key: 'fontSize', value: Number(e.target.value), schemaId: activeSchema.id }])
|
207
221
|
}}
|
208
222
|
/>
|
209
223
|
<NumberInputSet
|
210
224
|
width="30%"
|
211
225
|
label={'LineHeight(em)'}
|
226
|
+
step={0.1}
|
212
227
|
value={activeSchema.lineHeight ?? DEFAULT_LINE_HEIGHT}
|
213
228
|
onChange={(e) =>
|
214
229
|
changeSchemas([
|
@@ -220,6 +235,7 @@ const TextPropEditor = (
|
|
220
235
|
<NumberInputSet
|
221
236
|
width="40%"
|
222
237
|
label={'CharacterSpacing(pt)'}
|
238
|
+
step={0.1}
|
223
239
|
value={activeSchema.characterSpacing ?? DEFAULT_CHARACTER_SPACING}
|
224
240
|
onChange={async (e) => {
|
225
241
|
const currentCharacterSpacing = Number(e.target.value);
|
@@ -257,26 +273,46 @@ const TextPropEditor = (
|
|
257
273
|
{activeSchema.dynamicFontSize && (
|
258
274
|
<>
|
259
275
|
<NumberInputSet
|
260
|
-
width="
|
276
|
+
width="30%"
|
261
277
|
label={'FontSize Min(pt)'}
|
262
278
|
value={activeSchema.dynamicFontSize.min ?? Number(activeSchema.fontSize)}
|
263
279
|
minNumber={0}
|
264
|
-
|
280
|
+
style={
|
281
|
+
activeSchema.dynamicFontSize &&
|
282
|
+
activeSchema.dynamicFontSize.max < activeSchema.dynamicFontSize.min
|
283
|
+
? { background: 'rgb(200 0 0 / 30%)' }
|
284
|
+
: {}
|
285
|
+
}
|
265
286
|
onChange={(e) => {
|
266
287
|
changeSchemas([{ key: 'dynamicFontSize.min', value: Number(e.target.value), schemaId: activeSchema.id }])
|
267
|
-
|
268
288
|
}}
|
269
289
|
/>
|
270
290
|
|
271
291
|
<NumberInputSet
|
272
|
-
width="
|
292
|
+
width="30%"
|
273
293
|
label={'FontSize Max(pt)'}
|
274
294
|
value={activeSchema.dynamicFontSize.max ?? Number(activeSchema.fontSize)}
|
275
|
-
minNumber={
|
295
|
+
minNumber={0}
|
296
|
+
style={
|
297
|
+
activeSchema.dynamicFontSize &&
|
298
|
+
activeSchema.dynamicFontSize.max < activeSchema.dynamicFontSize.min
|
299
|
+
? { background: 'rgb(200 0 0 / 30%)' }
|
300
|
+
: {}
|
301
|
+
}
|
276
302
|
onChange={(e) => {
|
277
303
|
changeSchemas([{ key: 'dynamicFontSize.max', value: Number(e.target.value), schemaId: activeSchema.id }])
|
278
304
|
}}
|
279
305
|
/>
|
306
|
+
|
307
|
+
<SelectSet
|
308
|
+
width="40%"
|
309
|
+
label={'Fit'}
|
310
|
+
value={activeSchema.dynamicFontSize.fit ?? DEFAULT_DYNAMIC_FIT}
|
311
|
+
options={dynamicFits}
|
312
|
+
onChange={(e) => {
|
313
|
+
changeSchemas([{ key: 'dynamicFontSize.fit', value: e.target.value, schemaId: activeSchema.id }])
|
314
|
+
}}
|
315
|
+
/>
|
280
316
|
</>
|
281
317
|
)}
|
282
318
|
</div>
|
package/src/components/Paper.tsx
CHANGED
@@ -3,7 +3,7 @@ import { SchemaForUI, Size, getFallbackFontName } from '@pdfme/common';
|
|
3
3
|
import { FontContext } from '../contexts';
|
4
4
|
import { ZOOM, RULER_HEIGHT } from '../constants';
|
5
5
|
|
6
|
-
const Paper = (
|
6
|
+
const Paper = (props: {
|
7
7
|
paperRefs: MutableRefObject<HTMLDivElement[]>;
|
8
8
|
scale: number;
|
9
9
|
size: Size;
|
@@ -14,7 +14,7 @@ const Paper = (porps: {
|
|
14
14
|
renderSchema: (arg: { index: number; schema: SchemaForUI }) => ReactNode;
|
15
15
|
}) => {
|
16
16
|
const { paperRefs, scale, size, schemasList, pageSizes, backgrounds, renderPaper, renderSchema } =
|
17
|
-
|
17
|
+
props;
|
18
18
|
const font = useContext(FontContext);
|
19
19
|
|
20
20
|
if (pageSizes.length !== backgrounds.length || pageSizes.length !== schemasList.length) {
|
@@ -100,8 +100,9 @@ const Preview = ({ template, inputs, size, onChangeInput }: PreviewReactProps) =
|
|
100
100
|
key={schema.id}
|
101
101
|
schema={Object.assign(schema, { data })}
|
102
102
|
editable={editable}
|
103
|
-
placeholder={template.sampledata
|
103
|
+
placeholder={template.sampledata?.[0]?.[key] ?? ''}
|
104
104
|
tabIndex={index + 100}
|
105
|
+
onStopEditing={() => { }}
|
105
106
|
onChange={(value) => handleChangeInput({ key, value })}
|
106
107
|
outline={editable ? '1px dashed #4af' : 'transparent'}
|
107
108
|
/>
|
@@ -69,7 +69,7 @@ const BarcodePreview = (props: { schema: BarcodeSchema; value: string }) => {
|
|
69
69
|
type Props = SchemaUIProps & { schema: BarcodeSchema };
|
70
70
|
|
71
71
|
const BarcodeSchemaUI = (
|
72
|
-
{ schema, editable, placeholder, tabIndex, onChange }: Props,
|
72
|
+
{ schema, editable, placeholder, tabIndex, onChange, onStopEditing }: Props,
|
73
73
|
ref: Ref<HTMLInputElement>
|
74
74
|
) => {
|
75
75
|
const value = schema.data;
|
@@ -109,6 +109,7 @@ const BarcodeSchemaUI = (
|
|
109
109
|
style={style}
|
110
110
|
value={value}
|
111
111
|
onChange={(e) => onChange(e.target.value)}
|
112
|
+
onBlur={onStopEditing}
|
112
113
|
/>
|
113
114
|
) : (
|
114
115
|
<div style={style}>
|
@@ -8,7 +8,7 @@ import { XMarkIcon } from '@heroicons/react/24/outline';
|
|
8
8
|
type Props = SchemaUIProps & { schema: ImageSchema };
|
9
9
|
|
10
10
|
const ImageSchemaUI = (props: Props, ref: Ref<HTMLInputElement>) => {
|
11
|
-
const { editable, placeholder, tabIndex, schema, onChange } = props;
|
11
|
+
const { editable, placeholder, tabIndex, schema, onChange, onStopEditing } = props;
|
12
12
|
const [fileName, setFileName] = useState<string>('');
|
13
13
|
const hasData = Boolean(schema.data);
|
14
14
|
|
@@ -75,6 +75,7 @@ const ImageSchemaUI = (props: Props, ref: Ref<HTMLInputElement>) => {
|
|
75
75
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
76
76
|
readFiles(event.target.files, 'dataURL').then((result) => onChange(result as string))
|
77
77
|
}
|
78
|
+
onBlur={onStopEditing}
|
78
79
|
type="file"
|
79
80
|
accept="image/jpeg, image/png"
|
80
81
|
/>
|
@@ -2,79 +2,168 @@ import React, { useContext, forwardRef, Ref, useState, useEffect } from 'react';
|
|
2
2
|
import {
|
3
3
|
DEFAULT_FONT_SIZE,
|
4
4
|
DEFAULT_ALIGNMENT,
|
5
|
+
VERTICAL_ALIGN_TOP,
|
6
|
+
VERTICAL_ALIGN_MIDDLE,
|
7
|
+
VERTICAL_ALIGN_BOTTOM,
|
8
|
+
DEFAULT_VERTICAL_ALIGNMENT,
|
5
9
|
DEFAULT_LINE_HEIGHT,
|
6
10
|
DEFAULT_CHARACTER_SPACING,
|
7
11
|
DEFAULT_FONT_COLOR,
|
8
12
|
TextSchema,
|
9
13
|
calculateDynamicFontSize,
|
10
14
|
getFontKitFont,
|
11
|
-
|
15
|
+
getBrowserVerticalFontAdjustments,
|
12
16
|
} from '@pdfme/common';
|
13
17
|
import { SchemaUIProps } from './SchemaUI';
|
14
18
|
import { ZOOM } from '../../constants';
|
15
19
|
import { FontContext } from '../../contexts';
|
16
20
|
|
21
|
+
const mapVerticalAlignToFlex = (verticalAlignmentValue: string | undefined) => {
|
22
|
+
switch (verticalAlignmentValue) {
|
23
|
+
case VERTICAL_ALIGN_TOP:
|
24
|
+
return 'flex-start';
|
25
|
+
case VERTICAL_ALIGN_MIDDLE:
|
26
|
+
return 'center';
|
27
|
+
case VERTICAL_ALIGN_BOTTOM:
|
28
|
+
return 'flex-end';
|
29
|
+
}
|
30
|
+
return 'flex-start';
|
31
|
+
};
|
32
|
+
|
17
33
|
type Props = SchemaUIProps & { schema: TextSchema };
|
18
34
|
|
19
35
|
const TextSchemaUI = (
|
20
|
-
{ schema, editable, placeholder, tabIndex, onChange }: Props,
|
36
|
+
{ schema, editable, placeholder, tabIndex, onChange, onStopEditing }: Props,
|
21
37
|
ref: Ref<HTMLTextAreaElement>
|
22
38
|
) => {
|
23
39
|
const font = useContext(FontContext);
|
24
40
|
const [dynamicFontSize, setDynamicFontSize] = useState<number | undefined>(undefined);
|
25
|
-
const [
|
26
|
-
|
41
|
+
const [topAdjustment, setTopAdjustment] = useState<number>(0);
|
42
|
+
const [bottomAdjustment, setBottomAdjustment] = useState<number>(0);
|
27
43
|
|
28
44
|
useEffect(() => {
|
29
45
|
if (schema.dynamicFontSize && schema.data) {
|
30
|
-
calculateDynamicFontSize({
|
46
|
+
calculateDynamicFontSize({
|
47
|
+
textSchema: schema,
|
48
|
+
font,
|
49
|
+
input: schema.data,
|
50
|
+
startingFontSize: dynamicFontSize,
|
51
|
+
}).then(setDynamicFontSize);
|
31
52
|
} else {
|
32
53
|
setDynamicFontSize(undefined);
|
33
54
|
}
|
55
|
+
}, [
|
56
|
+
schema.data,
|
57
|
+
schema.width,
|
58
|
+
schema.height,
|
59
|
+
schema.dynamicFontSize?.min,
|
60
|
+
schema.dynamicFontSize?.max,
|
61
|
+
schema.dynamicFontSize?.fit,
|
62
|
+
schema.characterSpacing,
|
63
|
+
schema.lineHeight,
|
64
|
+
font
|
65
|
+
]);
|
66
|
+
|
67
|
+
useEffect(() => {
|
34
68
|
getFontKitFont(schema, font).then(fontKitFont => {
|
35
|
-
|
36
|
-
|
69
|
+
// Depending on vertical alignment, we need to move the top or bottom of the font to keep
|
70
|
+
// it within it's defined box and align it with the generated pdf.
|
71
|
+
const { topAdj, bottomAdj } = getBrowserVerticalFontAdjustments(
|
72
|
+
fontKitFont,
|
73
|
+
dynamicFontSize ?? schema.fontSize ?? DEFAULT_FONT_SIZE,
|
74
|
+
schema.lineHeight ?? DEFAULT_LINE_HEIGHT,
|
75
|
+
schema.verticalAlignment ?? DEFAULT_VERTICAL_ALIGNMENT
|
76
|
+
);
|
77
|
+
setTopAdjustment(topAdj);
|
78
|
+
setBottomAdjustment(bottomAdj);
|
37
79
|
});
|
38
|
-
}, [schema.data, schema.width, schema.fontName, schema.fontSize, schema.dynamicFontSize, schema.dynamicFontSize?.max, schema.dynamicFontSize?.min, schema.characterSpacing, font]);
|
39
80
|
|
81
|
+
if (ref && 'current' in ref) {
|
82
|
+
const textarea = ref.current;
|
40
83
|
|
41
|
-
|
84
|
+
if (textarea) {
|
85
|
+
// Textareas cannot be vertically aligned, so we need to adjust the height of the textarea
|
86
|
+
// to exactly fit the height of it's content, whilst aligned within it's container.
|
87
|
+
// This gives the appearance of being vertically aligned.
|
88
|
+
textarea.style.height = 'auto'; // Reset the height to auto to ensure we get the correct height.
|
89
|
+
textarea.style.height = `${textarea.scrollHeight}px`;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}, [
|
93
|
+
schema.data,
|
94
|
+
schema.width,
|
95
|
+
schema.height,
|
96
|
+
schema.fontName,
|
97
|
+
schema.fontSize,
|
98
|
+
schema.dynamicFontSize?.max,
|
99
|
+
schema.dynamicFontSize?.min,
|
100
|
+
schema.dynamicFontSize?.fit,
|
101
|
+
schema.characterSpacing,
|
102
|
+
schema.lineHeight,
|
103
|
+
schema.verticalAlignment,
|
104
|
+
font,
|
105
|
+
dynamicFontSize,
|
106
|
+
editable,
|
107
|
+
]);
|
108
|
+
|
109
|
+
const containerStyle: React.CSSProperties = {
|
42
110
|
position: 'absolute',
|
43
111
|
top: 0,
|
44
112
|
padding: 0,
|
45
|
-
height:
|
113
|
+
height: schema.height * ZOOM,
|
46
114
|
width: schema.width * ZOOM,
|
47
115
|
resize: 'none',
|
48
|
-
|
49
|
-
|
116
|
+
backgroundColor: schema.data && schema.backgroundColor ? schema.backgroundColor : 'rgb(242 244 255 / 75%)',
|
117
|
+
border: 'none',
|
118
|
+
display: 'flex',
|
119
|
+
flexDirection: 'column',
|
120
|
+
justifyContent: mapVerticalAlignToFlex(schema.verticalAlignment),
|
121
|
+
};
|
122
|
+
|
123
|
+
const textareaStyle: React.CSSProperties = {
|
124
|
+
padding: 0,
|
125
|
+
resize: 'none',
|
126
|
+
border: 'none',
|
127
|
+
outline: 'none',
|
128
|
+
paddingTop: topAdjustment,
|
129
|
+
background: 'none',
|
130
|
+
};
|
131
|
+
|
132
|
+
const fontStyles: React.CSSProperties = {
|
50
133
|
fontFamily: schema.fontName ? `'${schema.fontName}'` : 'inherit',
|
51
134
|
color: schema.fontColor ? schema.fontColor : DEFAULT_FONT_COLOR,
|
52
135
|
fontSize: `${dynamicFontSize ?? schema.fontSize ?? DEFAULT_FONT_SIZE}pt`,
|
53
136
|
letterSpacing: `${schema.characterSpacing ?? DEFAULT_CHARACTER_SPACING}pt`,
|
54
137
|
lineHeight: `${schema.lineHeight ?? DEFAULT_LINE_HEIGHT}em`,
|
55
138
|
textAlign: schema.alignment ?? DEFAULT_ALIGNMENT,
|
56
|
-
whiteSpace: 'pre-
|
139
|
+
whiteSpace: 'pre-wrap',
|
57
140
|
wordBreak: 'break-word',
|
58
|
-
backgroundColor:
|
59
|
-
schema.data && schema.backgroundColor ? schema.backgroundColor : 'rgb(242 244 255 / 75%)',
|
60
|
-
border: 'none',
|
61
141
|
};
|
62
142
|
|
63
143
|
return editable ? (
|
64
|
-
<
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
144
|
+
<div style={containerStyle}>
|
145
|
+
<textarea
|
146
|
+
ref={ref}
|
147
|
+
placeholder={placeholder}
|
148
|
+
tabIndex={tabIndex}
|
149
|
+
style={{ ...textareaStyle, ...fontStyles }}
|
150
|
+
onChange={(e) => onChange(e.target.value)}
|
151
|
+
onBlur={onStopEditing}
|
152
|
+
value={schema.data}
|
153
|
+
></textarea>
|
154
|
+
</div>
|
72
155
|
) : (
|
73
|
-
<div style={
|
74
|
-
<div
|
156
|
+
<div style={containerStyle}>
|
157
|
+
<div
|
158
|
+
style={{
|
159
|
+
...fontStyles,
|
160
|
+
marginBottom: bottomAdjustment,
|
161
|
+
paddingTop: topAdjustment,
|
162
|
+
}}
|
163
|
+
>
|
75
164
|
{/* Set the letterSpacing of the last character to 0. */}
|
76
165
|
{schema.data.split('').map((l, i) => (
|
77
|
-
<span key={i} style={{ letterSpacing: String(schema.data).length === i + 1 ? 0 : 'inherit'
|
166
|
+
<span key={i} style={{ letterSpacing: String(schema.data).length === i + 1 ? 0 : 'inherit' }}>
|
78
167
|
{l}
|
79
168
|
</span>
|
80
169
|
))}
|
package/src/helper.ts
CHANGED
@@ -314,7 +314,7 @@ const sortSchemasList = (template: Template, pageNum: number): SchemaForUI[][] =
|
|
314
314
|
})
|
315
315
|
.map((e) => {
|
316
316
|
const [key, value] = e;
|
317
|
-
const data = template.sampledata
|
317
|
+
const data = template.sampledata?.[0]?.[key] ?? '';
|
318
318
|
|
319
319
|
return Object.assign(value, {
|
320
320
|
key,
|