@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.
@@ -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";
@@ -1,6 +1,6 @@
1
1
  import { MutableRefObject, ReactNode } from 'react';
2
2
  import { SchemaForUI, Size } from '@pdfme/common';
3
- declare const Paper: (porps: {
3
+ declare const Paper: (props: {
4
4
  paperRefs: MutableRefObject<HTMLDivElement[]>;
5
5
  scale: number;
6
6
  size: Size;
@@ -4,6 +4,7 @@ export interface SchemaUIProps {
4
4
  schema: SchemaForUI;
5
5
  editable: boolean;
6
6
  onChange: (value: string) => void;
7
+ onStopEditing: () => void;
7
8
  tabIndex?: number;
8
9
  placeholder?: string;
9
10
  }
@@ -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>>;
@@ -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.2",
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.0.0"
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={10} height={10} />
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={async (value) => {
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={'Alignment'}
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
- const currentFontSize = Number(e.target.value);
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="45%"
276
+ width="30%"
261
277
  label={'FontSize Min(pt)'}
262
278
  value={activeSchema.dynamicFontSize.min ?? Number(activeSchema.fontSize)}
263
279
  minNumber={0}
264
- maxNumber={activeSchema.fontSize}
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="45%"
292
+ width="30%"
273
293
  label={'FontSize Max(pt)'}
274
294
  value={activeSchema.dynamicFontSize.max ?? Number(activeSchema.fontSize)}
275
- minNumber={activeSchema.fontSize}
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>
@@ -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 = (porps: {
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
- porps;
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 ? template.sampledata[0][key] : ''}
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
  />
@@ -9,6 +9,7 @@ export interface SchemaUIProps {
9
9
  schema: SchemaForUI;
10
10
  editable: boolean;
11
11
  onChange: (value: string) => void;
12
+ onStopEditing: () => void;
12
13
  tabIndex?: number;
13
14
  placeholder?: string;
14
15
  }
@@ -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
- getFontAlignmentValue,
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 [fontAlignmentValue, setFontAlignmentValue] = useState<number>(0);
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({ textSchema: schema, font, input: schema.data }).then(setDynamicFontSize)
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
- const fav = getFontAlignmentValue(fontKitFont, dynamicFontSize ?? schema.fontSize ?? DEFAULT_FONT_SIZE);
36
- setFontAlignmentValue(fav);
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
- const style: React.CSSProperties = {
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: fontAlignmentValue < 0 ? schema.height * ZOOM + Math.abs(fontAlignmentValue) : schema.height * ZOOM,
113
+ height: schema.height * ZOOM,
46
114
  width: schema.width * ZOOM,
47
115
  resize: 'none',
48
- marginTop: fontAlignmentValue < 0 ? fontAlignmentValue : 0,
49
- paddingTop: fontAlignmentValue >= 0 ? fontAlignmentValue : 0,
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-line',
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
- <textarea
65
- ref={ref}
66
- placeholder={placeholder}
67
- tabIndex={tabIndex}
68
- style={style}
69
- onChange={(e) => onChange(e.target.value)}
70
- value={schema.data}
71
- ></textarea>
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={{ ...style, height: schema.height * ZOOM, marginTop: 0, paddingTop: 0 }}>
74
- <div style={{ marginTop: style.marginTop, paddingTop: style.paddingTop }}>
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 ? template.sampledata[0][key] : '';
317
+ const data = template.sampledata?.[0]?.[key] ?? '';
318
318
 
319
319
  return Object.assign(value, {
320
320
  key,