@pdfme/ui 2.2.0 → 3.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +33 -35
  2. package/__mocks__/form-render.js +7 -0
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.LICENSE.txt +42 -4
  5. package/dist/index.js.map +1 -1
  6. package/dist/types/Designer.d.ts +3 -0
  7. package/dist/types/builtInPropPanel.d.ts +3 -0
  8. package/dist/types/builtInRenderer.d.ts +3 -0
  9. package/dist/types/class.d.ts +18 -38
  10. package/dist/types/components/CtlBar/Pager.d.ts +3 -2
  11. package/dist/types/components/CtlBar/Zoom.d.ts +3 -2
  12. package/dist/types/components/CtlBar/index.d.ts +3 -2
  13. package/dist/types/components/Designer/{Main → Canvas}/Guides.d.ts +2 -2
  14. package/dist/types/components/Designer/Canvas/Mask.d.ts +4 -0
  15. package/dist/types/components/Designer/Canvas/Moveable.d.ts +37 -0
  16. package/dist/types/components/Designer/{Main → Canvas}/Selecto.d.ts +2 -1
  17. package/dist/types/components/Designer/{Main → Canvas}/index.d.ts +3 -6
  18. package/dist/types/components/Designer/Sidebar/DetailView/AlignWidget.d.ts +4 -0
  19. package/dist/types/components/Designer/Sidebar/DetailView/WidgetRenderer.d.ts +7 -0
  20. package/dist/types/components/Designer/Sidebar/DetailView/index.d.ts +5 -4
  21. package/dist/types/components/Designer/Sidebar/ListView/SelectableSortableContainer.d.ts +3 -2
  22. package/dist/types/components/Designer/Sidebar/ListView/SelectableSortableItem.d.ts +1 -1
  23. package/dist/types/components/Designer/Sidebar/ListView/index.d.ts +3 -2
  24. package/dist/types/components/Designer/Sidebar/index.d.ts +3 -23
  25. package/dist/types/components/Designer/index.d.ts +6 -107
  26. package/dist/types/components/Divider.d.ts +2 -1
  27. package/dist/types/components/ErrorScreen.d.ts +7 -0
  28. package/dist/types/components/Paper.d.ts +3 -2
  29. package/dist/types/components/Preview.d.ts +10 -2
  30. package/dist/types/components/Renderer.d.ts +10 -0
  31. package/dist/types/components/Root.d.ts +1 -1
  32. package/dist/types/components/Spinner.d.ts +2 -1
  33. package/dist/types/components/UnitPager.d.ts +3 -2
  34. package/dist/types/constants.d.ts +3 -3
  35. package/dist/types/contexts.d.ts +4 -1
  36. package/dist/types/helper.d.ts +4 -46
  37. package/dist/types/hooks.d.ts +2 -2
  38. package/dist/types/i18n.d.ts +4 -2
  39. package/dist/types/index.d.ts +1 -4
  40. package/dist/types/types.d.ts +25 -0
  41. package/package.json +19 -8
  42. package/src/Designer.tsx +69 -21
  43. package/src/Form.tsx +18 -14
  44. package/src/Viewer.tsx +6 -2
  45. package/src/builtInPropPanel.ts +5 -0
  46. package/src/builtInRenderer.ts +5 -0
  47. package/src/class.ts +25 -2
  48. package/src/components/CtlBar/index.tsx +4 -7
  49. package/src/components/Designer/{Main → Canvas}/Guides.tsx +2 -2
  50. package/src/components/Designer/{Main → Canvas}/Moveable.tsx +23 -19
  51. package/src/components/Designer/{Main → Canvas}/index.tsx +77 -30
  52. package/src/components/Designer/Sidebar/DetailView/AlignWidget.tsx +182 -0
  53. package/src/components/Designer/Sidebar/DetailView/WidgetRenderer.tsx +28 -0
  54. package/src/components/Designer/Sidebar/DetailView/index.tsx +153 -22
  55. package/src/components/Designer/Sidebar/ListView/Item.tsx +1 -1
  56. package/src/components/Designer/Sidebar/ListView/SelectableSortableContainer.tsx +6 -6
  57. package/src/components/Designer/Sidebar/ListView/index.tsx +1 -4
  58. package/src/components/Designer/Sidebar/index.tsx +26 -60
  59. package/src/components/Designer/index.tsx +53 -32
  60. package/src/components/{Error.tsx → ErrorScreen.tsx} +2 -2
  61. package/src/components/Paper.tsx +35 -9
  62. package/src/components/Preview.tsx +48 -50
  63. package/src/components/Renderer.tsx +90 -0
  64. package/src/components/Root.tsx +5 -1
  65. package/src/constants.ts +4 -4
  66. package/src/contexts.ts +7 -0
  67. package/src/helper.ts +19 -122
  68. package/src/hooks.ts +6 -5
  69. package/src/i18n.ts +48 -11
  70. package/src/index.ts +1 -76
  71. package/src/types.ts +36 -0
  72. package/tsconfig.json +2 -1
  73. package/webpack.config.js +6 -1
  74. package/dist/types/components/Designer/Main/Mask.d.ts +0 -3
  75. package/dist/types/components/Designer/Main/Moveable.d.ts +0 -31
  76. package/dist/types/components/Designer/Sidebar/DetailView/ExampleInputEditor.d.ts +0 -6
  77. package/dist/types/components/Designer/Sidebar/DetailView/PositionAndSizeEditor.d.ts +0 -6
  78. package/dist/types/components/Designer/Sidebar/DetailView/TextPropEditor.d.ts +0 -6
  79. package/dist/types/components/Designer/Sidebar/DetailView/TypeAndKeyEditor.d.ts +0 -6
  80. package/dist/types/components/Error.d.ts +0 -6
  81. package/dist/types/components/Schemas/BarcodeSchema.d.ts +0 -15
  82. package/dist/types/components/Schemas/ImageSchema.d.ts +0 -15
  83. package/dist/types/components/Schemas/SchemaUI.d.ts +0 -15
  84. package/dist/types/components/Schemas/TextSchema.d.ts +0 -28
  85. package/src/components/Designer/Sidebar/DetailView/ExampleInputEditor.tsx +0 -85
  86. package/src/components/Designer/Sidebar/DetailView/PositionAndSizeEditor.tsx +0 -275
  87. package/src/components/Designer/Sidebar/DetailView/TextPropEditor.tsx +0 -357
  88. package/src/components/Designer/Sidebar/DetailView/TypeAndKeyEditor.tsx +0 -87
  89. package/src/components/Schemas/BarcodeSchema.tsx +0 -124
  90. package/src/components/Schemas/ImageSchema.tsx +0 -87
  91. package/src/components/Schemas/SchemaUI.tsx +0 -62
  92. package/src/components/Schemas/TextSchema.tsx +0 -175
  93. /package/src/components/Designer/{Main → Canvas}/Mask.tsx +0 -0
  94. /package/src/components/Designer/{Main → Canvas}/Selecto.tsx +0 -0
@@ -1,31 +1,33 @@
1
1
  import React, {
2
2
  Ref,
3
+ useMemo,
4
+ useContext,
3
5
  MutableRefObject,
4
6
  useRef,
5
7
  useState,
6
8
  useEffect,
7
9
  forwardRef,
8
10
  useCallback,
9
- useContext,
10
11
  } from 'react';
11
- import { OnDrag, OnResize, OnClick } from 'react-moveable';
12
- import { SchemaForUI, Size } from '@pdfme/common';
12
+ import { OnDrag, OnResize, OnClick, OnRotate } from 'react-moveable';
13
+ import { ZOOM, SchemaForUI, Size, ChangeSchemas } from '@pdfme/common';
14
+ import { PropPanelRegistry } from '../../../contexts';
13
15
  import { XMarkIcon } from '@heroicons/react/24/outline';
14
- import { ZOOM, RULER_HEIGHT } from '../../../constants';
16
+ import { RULER_HEIGHT, SIDEBAR_WIDTH } from '../../../constants';
15
17
  import { usePrevious } from '../../../hooks';
16
18
  import { uuid, round, flatten } from '../../../helper';
17
19
  import Paper from '../../Paper';
18
- import SchemaUI from '../../Schemas/SchemaUI';
20
+ import Renderer from '../../Renderer';
19
21
  import Selecto from './Selecto';
20
22
  import Moveable from './Moveable';
21
23
  import Guides from './Guides';
22
24
  import Mask from './Mask';
23
- import { FontContext } from '../../../contexts';
24
25
 
25
26
  const DELETE_BTN_ID = uuid();
26
27
  const fmt4Num = (prop: string) => Number(prop.replace('px', ''));
27
28
  const fmt = (prop: string) => round(fmt4Num(prop) / ZOOM, 2);
28
29
  const isTopLeftResize = (d: string) => d === '-1,-1' || d === '-1,0' || d === '0,-1';
30
+ const normalizeRotate = (angle: number) => ((angle % 360) + 360) % 360;
29
31
 
30
32
  const DeleteButton = ({ activeElements: aes }: { activeElements: HTMLElement[] }) => {
31
33
  const top = Math.min(...aes.map(({ style }) => fmt4Num(style.top)));
@@ -78,12 +80,13 @@ interface Props {
78
80
  size: Size;
79
81
  activeElements: HTMLElement[];
80
82
  onEdit: (targets: HTMLElement[]) => void;
81
- changeSchemas: (objs: { key: string; value: string | number; schemaId: string }[]) => void;
83
+ changeSchemas: ChangeSchemas;
82
84
  removeSchemas: (ids: string[]) => void;
83
85
  paperRefs: MutableRefObject<HTMLDivElement[]>;
86
+ sidebarOpen: boolean;
84
87
  }
85
88
 
86
- const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
89
+ const Canvas = (props: Props, ref: Ref<HTMLDivElement>) => {
87
90
  const {
88
91
  pageCursor,
89
92
  scale,
@@ -93,14 +96,18 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
93
96
  activeElements,
94
97
  schemasList,
95
98
  hoveringSchemaId,
99
+ onEdit,
100
+ changeSchemas,
101
+ removeSchemas,
102
+ onChangeHoveringSchemaId,
103
+ paperRefs,
104
+ sidebarOpen,
96
105
  } = props;
97
- const { onEdit, changeSchemas, removeSchemas, onChangeHoveringSchemaId, paperRefs } = props;
106
+ const propPanelRegistry = useContext(PropPanelRegistry);
98
107
 
99
- const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
100
108
  const verticalGuides = useRef<GuidesInterface[]>([]);
101
109
  const horizontalGuides = useRef<GuidesInterface[]>([]);
102
110
  const moveable = useRef<any>(null);
103
- const font = useContext(FontContext);
104
111
 
105
112
  const [isPressShiftKey, setIsPressShiftKey] = useState(false);
106
113
  const [editing, setEditing] = useState(false);
@@ -166,6 +173,26 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
166
173
  changeSchemas(flatten(arg));
167
174
  };
168
175
 
176
+ const onRotate = ({ target, rotate }: OnRotate) => {
177
+ target.style.transform = `rotate(${rotate}deg)`;
178
+ };
179
+
180
+ const onRotateEnd = ({ target }: { target: HTMLElement | SVGElement }) => {
181
+ const { transform } = target.style;
182
+ const rotate = Number(transform.replace('rotate(', '').replace('deg)', ''));
183
+ const normalizedRotate = normalizeRotate(rotate);
184
+ changeSchemas([{ key: 'rotate', value: normalizedRotate, schemaId: target.id }]);
185
+ };
186
+
187
+ const onRotateEnds = ({ targets }: { targets: (HTMLElement | SVGElement)[] }) => {
188
+ const arg = targets.map(({ style: { transform }, id }) => {
189
+ const rotate = Number(transform.replace('rotate(', '').replace('deg)', ''));
190
+ const normalizedRotate = normalizeRotate(rotate);
191
+ return [{ key: 'rotate', value: normalizedRotate, schemaId: id }];
192
+ });
193
+ changeSchemas(flatten(arg));
194
+ };
195
+
169
196
  const onResizeEnd = async ({ target }: { target: HTMLElement | SVGElement }) => {
170
197
  const { id, style } = target;
171
198
  const { width, height, top, left } = style;
@@ -223,16 +250,29 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
223
250
  const onClickMoveable = (e: OnClick) => {
224
251
  e.inputEvent.stopPropagation();
225
252
  setEditing(true);
226
- const ic = inputRef.current;
227
- if (!ic) return;
228
- ic.focus();
229
- if (ic.type !== 'file') {
230
- ic.setSelectionRange(ic.value.length, ic.value.length);
231
- }
232
253
  };
233
254
 
255
+ const rotatable = useMemo(() => {
256
+ const selectedSchemas = schemasList[pageCursor].filter((s) =>
257
+ activeElements.map((ae) => ae.id).includes(s.id)
258
+ );
259
+ const schemaTypes = selectedSchemas.map((s) => s.type);
260
+ const uniqueSchemaTypes = [...new Set(schemaTypes)];
261
+ return uniqueSchemaTypes.every(
262
+ (type) => propPanelRegistry[type]?.defaultSchema?.rotate !== undefined
263
+ );
264
+ }, [activeElements, pageCursor, schemasList]);
265
+
234
266
  return (
235
- <div ref={ref} style={{ overflow: 'overlay' }}>
267
+ <div
268
+ style={{
269
+ position: 'relative',
270
+ overflow: 'auto',
271
+ marginRight: sidebarOpen ? SIDEBAR_WIDTH : 0,
272
+ ...size,
273
+ }}
274
+ ref={ref}
275
+ >
236
276
  <Selecto
237
277
  container={paperRefs.current[pageCursor]}
238
278
  continueSelect={isPressShiftKey}
@@ -277,6 +317,7 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
277
317
  schemasList={schemasList}
278
318
  pageSizes={pageSizes}
279
319
  backgrounds={backgrounds}
320
+ hasRulers={true}
280
321
  renderPaper={({ index, paperSize }) => (
281
322
  <>
282
323
  {!editing && activeElements.length > 0 && (
@@ -285,18 +326,17 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
285
326
  <Guides
286
327
  paperSize={paperSize}
287
328
  horizontalRef={(e) => {
288
- if (e) {
289
- horizontalGuides.current[index] = e;
290
- }
329
+ if (e) horizontalGuides.current[index] = e;
291
330
  }}
292
331
  verticalRef={(e) => {
293
- if (e) {
294
- verticalGuides.current[index] = e;
295
- }
332
+ if (e) verticalGuides.current[index] = e;
296
333
  }}
297
334
  />
298
335
  {pageCursor !== index ? (
299
- <Mask width={paperSize.width + RULER_HEIGHT} height={paperSize.height} />
336
+ <Mask
337
+ width={paperSize.width + RULER_HEIGHT}
338
+ height={paperSize.height + RULER_HEIGHT}
339
+ />
300
340
  ) : (
301
341
  !editing && (
302
342
  <Moveable
@@ -306,9 +346,13 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
306
346
  horizontalGuidelines={getGuideLines(horizontalGuides.current, index)}
307
347
  verticalGuidelines={getGuideLines(verticalGuides.current, index)}
308
348
  keepRatio={isPressShiftKey}
349
+ rotatable={rotatable}
309
350
  onDrag={onDrag}
310
351
  onDragEnd={onDragEnd}
311
352
  onDragGroupEnd={onDragEnds}
353
+ onRotate={onRotate}
354
+ onRotateEnd={onRotateEnd}
355
+ onRotateGroupEnd={onRotateEnds}
312
356
  onResize={onResize}
313
357
  onResizeEnd={onResizeEnd}
314
358
  onResizeGroupEnd={onResizeEnds}
@@ -319,21 +363,24 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
319
363
  </>
320
364
  )}
321
365
  renderSchema={({ schema }) => (
322
- <SchemaUI
366
+ <Renderer
323
367
  key={schema.id}
324
368
  schema={schema}
325
369
  onChangeHoveringSchemaId={onChangeHoveringSchemaId}
326
- editable={editing && activeElements.map((ae) => ae.id).includes(schema.id)}
370
+ mode={
371
+ editing && activeElements.map((ae) => ae.id).includes(schema.id)
372
+ ? 'designer'
373
+ : 'viewer'
374
+ }
327
375
  onChange={(value) => {
328
376
  changeSchemas([{ key: 'data', value, schemaId: schema.id }]);
329
377
  }}
330
- onStopEditing={() => setEditing(false)}
378
+ stopEditing={() => setEditing(false)}
331
379
  outline={hoveringSchemaId === schema.id ? '1px solid #18a0fb' : '1px dashed #4af'}
332
- ref={inputRef}
333
380
  />
334
381
  )}
335
382
  />
336
383
  </div>
337
384
  );
338
385
  };
339
- export default forwardRef<HTMLDivElement, Props>(Main);
386
+ export default forwardRef<HTMLDivElement, Props>(Canvas);
@@ -0,0 +1,182 @@
1
+ import { Button, Form } from 'antd';
2
+ import React from 'react';
3
+ import type { PropPanelWidgetProps } from '@pdfme/common';
4
+ import { round } from '../../../../helper';
5
+
6
+ const svgBaseProp = {
7
+ style: { width: '90%', height: '90%' },
8
+ xmlns: 'http://www.w3.org/2000/svg',
9
+ enableBackground: 'new 0 0 24 24',
10
+ height: '24px',
11
+ viewBox: '0 0 24 24',
12
+ width: '24px',
13
+ fill: '#000000',
14
+ };
15
+
16
+ const AlignWidget = ({
17
+ activeElements,
18
+ changeSchemas,
19
+ schemas,
20
+ pageSize,
21
+ }: PropPanelWidgetProps) => {
22
+ const align = (type: 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom') => {
23
+ const ids = activeElements.map((ae) => ae.id);
24
+ const ass = schemas.filter((s) => ids.includes(s.id));
25
+
26
+ const isVertical = ['left', 'center', 'right'].includes(type);
27
+ const tgtPos = isVertical ? 'x' : 'y';
28
+ const tgtSize = isVertical ? 'width' : 'height';
29
+ const isSingle = ass.length === 1;
30
+ const root = pageSize[tgtSize];
31
+
32
+ const min = isSingle ? 0 : Math.min(...ass.map((as) => as.position[tgtPos]));
33
+ const max = isSingle ? root : Math.max(...ass.map((as) => as.position[tgtPos] + as[tgtSize]));
34
+
35
+ let basePos = min;
36
+ let adjust = (_: number) => 0;
37
+
38
+ if (['center', 'middle'].includes(type)) {
39
+ basePos = (min + max) / 2;
40
+ adjust = (num: number) => num / 2;
41
+ } else if (['right', 'bottom'].includes(type)) {
42
+ basePos = max;
43
+ adjust = (num: number) => num;
44
+ }
45
+
46
+ changeSchemas(
47
+ ass.map((as) => ({
48
+ key: `position.${tgtPos}`,
49
+ value: round(basePos - adjust(as[tgtSize]), 2),
50
+ schemaId: as.id,
51
+ }))
52
+ );
53
+ };
54
+
55
+ const distribute = (type: 'vertical' | 'horizontal') => {
56
+ const ids = activeElements.map((ae) => ae.id);
57
+ const ass = schemas.filter((s) => ids.includes(s.id));
58
+
59
+ const isVertical = type === 'vertical';
60
+ const tgtPos = isVertical ? 'y' : 'x';
61
+ const tgtSize = isVertical ? 'height' : 'width';
62
+ const min = Math.min(...ass.map((as) => as.position[tgtPos]));
63
+ const max = Math.max(...ass.map((as) => as.position[tgtPos] + as[tgtSize]));
64
+
65
+ if (ass.length < 3) return;
66
+
67
+ const boxPos = min;
68
+ const boxSize = max - min;
69
+ const sum = ass.reduce((acc, cur) => acc + cur[tgtSize], 0);
70
+ const remain = boxSize - sum;
71
+ const unit = remain / (ass.length - 1);
72
+
73
+ let prev = 0;
74
+ changeSchemas(
75
+ ass.map((as, index) => {
76
+ prev += index === 0 ? 0 : ass[index - 1][tgtSize] + unit;
77
+ const value = round(boxPos + prev, 2);
78
+ return { key: `position.${tgtPos}`, value, schemaId: as.id };
79
+ })
80
+ );
81
+ };
82
+
83
+ const layoutBtns: { id: string; icon: any; onClick: () => void }[] = [
84
+ {
85
+ id: 'left',
86
+ icon: (
87
+ <svg {...svgBaseProp}>
88
+ <rect fill="none" height="24" width="24" />
89
+ <path d="M4,22H2V2h2V22z M22,7H6v3h16V7z M16,14H6v3h10V14z" />
90
+ </svg>
91
+ ),
92
+ onClick: () => align('left'),
93
+ },
94
+ {
95
+ id: 'center',
96
+ icon: (
97
+ <svg {...svgBaseProp}>
98
+ <rect fill="none" height="24" width="24" />
99
+ <polygon points="11,2 13,2 13,7 21,7 21,10 13,10 13,14 18,14 18,17 13,17 13,22 11,22 11,17 6,17 6,14 11,14 11,10 3,10 3,7 11,7" />
100
+ </svg>
101
+ ),
102
+ onClick: () => align('center'),
103
+ },
104
+ {
105
+ id: 'right',
106
+ icon: (
107
+ <svg {...svgBaseProp}>
108
+ <rect fill="none" height="24" width="24" />
109
+ <path d="M20,2h2v20h-2V2z M2,10h16V7H2V10z M8,17h10v-3H8V17z" />
110
+ </svg>
111
+ ),
112
+ onClick: () => align('right'),
113
+ },
114
+ {
115
+ id: 'top',
116
+ icon: (
117
+ <svg {...svgBaseProp}>
118
+ <rect fill="none" height="24" width="24" />
119
+ <path d="M22,2v2H2V2H22z M7,22h3V6H7V22z M14,16h3V6h-3V16z" />
120
+ </svg>
121
+ ),
122
+ onClick: () => align('top'),
123
+ },
124
+ {
125
+ id: 'middle',
126
+ icon: (
127
+ <svg {...svgBaseProp}>
128
+ <rect fill="none" height="24" width="24" />
129
+ <polygon points="22,11 17,11 17,6 14,6 14,11 10,11 10,3 7,3 7,11 1.84,11 1.84,13 7,13 7,21 10,21 10,13 14,13 14,18 17,18 17,13 22,13" />
130
+ </svg>
131
+ ),
132
+ onClick: () => align('middle'),
133
+ },
134
+ {
135
+ id: 'bottom',
136
+ icon: (
137
+ <svg {...svgBaseProp}>
138
+ <rect fill="none" height="24" width="24" />
139
+ <path d="M22,22H2v-2h20V22z M10,2H7v16h3V2z M17,8h-3v10h3V8z" />
140
+ </svg>
141
+ ),
142
+ onClick: () => align('bottom'),
143
+ },
144
+ {
145
+ id: 'vertical',
146
+ icon: (
147
+ <svg {...svgBaseProp}>
148
+ <rect fill="none" height="24" width="24" />
149
+ <path d="M22,2v2H2V2H22z M7,10.5v3h10v-3H7z M2,20v2h20v-2H2z" />
150
+ </svg>
151
+ ),
152
+ onClick: () => distribute('vertical'),
153
+ },
154
+ {
155
+ id: 'horizontal',
156
+ icon: (
157
+ <svg {...svgBaseProp}>
158
+ <rect fill="none" height="24" width="24" />
159
+ <path d="M4,22H2V2h2V22z M22,2h-2v20h2V2z M13.5,7h-3v10h3V7z" />
160
+ </svg>
161
+ ),
162
+ onClick: () => distribute('horizontal'),
163
+ },
164
+ ];
165
+
166
+ return (
167
+ <Form.Item label="Align">
168
+ <Button.Group>
169
+ {layoutBtns.map((btn) => (
170
+ <Button
171
+ key={btn.id}
172
+ style={{ padding: 7 }}
173
+ disabled={activeElements.length <= 2 && ['vertical', 'horizontal'].includes(btn.id)}
174
+ {...btn}
175
+ />
176
+ ))}
177
+ </Button.Group>
178
+ </Form.Item>
179
+ );
180
+ };
181
+
182
+ export default AlignWidget;
@@ -0,0 +1,28 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import type { PropPanelWidgetProps } from '@pdfme/common';
3
+
4
+ type Props = PropPanelWidgetProps & {
5
+ widget: (props: PropPanelWidgetProps) => void;
6
+ };
7
+
8
+ const WidgetRenderer = (props: Props) => {
9
+ const { widget, ...otherProps } = props;
10
+ const ref = useRef<HTMLDivElement>(null);
11
+
12
+ useEffect(() => {
13
+ if (ref.current) {
14
+ ref.current.innerHTML = '';
15
+ widget({ ...otherProps, rootElement: ref.current });
16
+ }
17
+
18
+ return () => {
19
+ if (ref.current) {
20
+ ref.current.innerHTML = '';
21
+ }
22
+ };
23
+ }, [props.activeSchema]);
24
+
25
+ return <div ref={ref} />;
26
+ };
27
+
28
+ export default WidgetRenderer;
@@ -1,41 +1,172 @@
1
- import React, { useContext } from 'react';
2
- import { SchemaForUI } from '@pdfme/common';
3
- import { I18nContext } from '../../../../contexts';
1
+ import FormRender, { useForm } from 'form-render';
2
+ import React, { useContext, useEffect, useState } from 'react';
3
+ import type { SchemaForUI, PropPanelWidgetProps, PropPanelSchema } from '@pdfme/common';
4
+ import type { SidebarProps } from '../../../../types';
5
+ import { Bars3Icon } from '@heroicons/react/20/solid';
6
+ import { I18nContext, PropPanelRegistry, OptionsContext } from '../../../../contexts';
7
+ import { RULER_HEIGHT } from '../../../../constants';
4
8
  import Divider from '../../../Divider';
5
- import { SidebarProps } from '../index';
6
- import TextPropEditor from './TextPropEditor';
7
- import ExampleInputEditor from './ExampleInputEditor';
8
- import PositionAndSizeEditor from './PositionAndSizeEditor';
9
- import TypeAndKeyEditor from './TypeAndKeyEditor';
9
+ import AlignWidget from './AlignWidget';
10
+ import WidgetRenderer from './WidgetRenderer';
10
11
 
11
12
  const DetailView = (
12
- props: Pick<SidebarProps, 'schemas' | 'pageSize' | 'changeSchemas' | 'activeElements'> & {
13
+ props: Pick<
14
+ SidebarProps,
15
+ 'size' | 'schemas' | 'pageSize' | 'changeSchemas' | 'activeElements' | 'deselectSchema'
16
+ > & {
13
17
  activeSchema: SchemaForUI;
14
18
  }
15
19
  ) => {
16
- const { activeSchema } = props;
20
+ const { size, changeSchemas, deselectSchema, activeSchema, activeElements } = props;
21
+ const form = useForm();
22
+
17
23
  const i18n = useContext(I18nContext);
24
+ const propPanelRegistry = useContext(PropPanelRegistry);
25
+ const options = useContext(OptionsContext);
26
+
27
+ const [widgets, setWidgets] = useState<{
28
+ [key: string]: (props: PropPanelWidgetProps) => React.JSX.Element;
29
+ }>({});
30
+
31
+ useEffect(() => {
32
+ const newWidgets: typeof widgets = {
33
+ AlignWidget: (p) => <AlignWidget {...p} {...props} options={options} />,
34
+ Divider,
35
+ };
36
+ for (const propPanel of Object.values(propPanelRegistry)) {
37
+ const widgets = propPanel?.widgets || {};
38
+ Object.entries(widgets).forEach(([widgetKey, widgetValue]) => {
39
+ newWidgets[widgetKey] = (p) => (
40
+ <WidgetRenderer {...p} {...props} options={options} widget={widgetValue} />
41
+ );
42
+ });
43
+ }
44
+ setWidgets(newWidgets);
45
+ }, [activeSchema, activeElements, propPanelRegistry]);
46
+
47
+ useEffect(() => {
48
+ form.setValues({ ...activeSchema });
49
+ }, [activeSchema]);
50
+
51
+ const handleWatch = (newSchema: any) => {
52
+ const changes = [];
53
+ for (const key in newSchema) {
54
+ if (['id', 'data'].includes(key)) continue;
55
+ if (newSchema[key] !== (activeSchema as any)[key]) {
56
+ changes.push({ key, value: newSchema[key], schemaId: activeSchema.id });
57
+ }
58
+ }
59
+ if (changes.length) {
60
+ changeSchemas(changes);
61
+ }
62
+ };
63
+
64
+ const activePropPanelRegistry = propPanelRegistry[activeSchema.type];
65
+ const activePropPanelSchema = activePropPanelRegistry?.propPanelSchema;
66
+ if (!activePropPanelSchema) {
67
+ console.error(`[@pdfme/ui] No propPanel.propPanelSchema for ${activeSchema.type}.
68
+ Check this document: https://pdfme.com/docs/custom-schemas`);
69
+ }
70
+
71
+ const propPanelSchema: PropPanelSchema = {
72
+ type: 'object',
73
+ column: 2,
74
+ properties: {
75
+ type: {
76
+ title: 'Type',
77
+ type: 'string',
78
+ widget: 'select',
79
+ props: {
80
+ options: Object.keys(propPanelRegistry).map((label) => ({ label, value: label })),
81
+ },
82
+ },
83
+ key: { title: 'Name', type: 'string', widget: 'input' },
84
+ '-': { type: 'void', widget: 'Divider', cellSpan: 2 },
85
+ align: { title: 'Align', type: 'void', widget: 'AlignWidget', cellSpan: 2 },
86
+ position: {
87
+ type: 'object',
88
+ widget: 'card',
89
+ properties: {
90
+ x: { title: 'X', type: 'number', widget: 'inputNumber' },
91
+ y: { title: 'Y', type: 'number', widget: 'inputNumber' },
92
+ },
93
+ },
94
+ width: { title: 'Width', type: 'number', widget: 'inputNumber', span: 8 },
95
+ height: { title: 'Height', type: 'number', widget: 'inputNumber', span: 8 },
96
+ rotate: {
97
+ title: 'Rotate',
98
+ type: 'number',
99
+ widget: 'inputNumber',
100
+ span: 8,
101
+ disabled: activePropPanelRegistry?.defaultSchema?.rotate === undefined,
102
+ max: 360,
103
+ min: 0,
104
+ },
105
+ },
106
+ };
107
+
108
+ if (typeof activePropPanelSchema === 'function') {
109
+ const apps = activePropPanelSchema({ ...props, options }) || {};
110
+ propPanelSchema.properties = {
111
+ ...propPanelSchema.properties,
112
+ ...(Object.keys(apps).length === 0
113
+ ? {}
114
+ : { '--': { type: 'void', widget: 'Divider', cellSpan: 2 } }),
115
+ ...apps,
116
+ };
117
+ } else {
118
+ const apps = activePropPanelSchema || {};
119
+ propPanelSchema.properties = {
120
+ ...propPanelSchema.properties,
121
+ ...(Object.keys(apps).length === 0
122
+ ? {}
123
+ : { '--': { type: 'void', widget: 'Divider', cellSpan: 2 } }),
124
+ ...apps,
125
+ };
126
+ }
18
127
 
19
128
  return (
20
129
  <div>
21
130
  <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
131
+ <span
132
+ style={{
133
+ position: 'absolute',
134
+ top: '0.75rem',
135
+ zIndex: 100,
136
+ border: 'none',
137
+ borderRadius: 2,
138
+ padding: '0.5rem',
139
+ cursor: 'pointer',
140
+ background: '#eee',
141
+ display: 'flex',
142
+ alignItems: 'center',
143
+ justifyContent: 'center',
144
+ maxWidth: 30,
145
+ maxHeight: 30,
146
+ }}
147
+ onClick={deselectSchema}
148
+ >
149
+ <Bars3Icon width={15} height={15} />
150
+ </span>
22
151
  <span style={{ textAlign: 'center', width: '100%', fontWeight: 'bold' }}>
23
152
  {i18n('editField')}
24
153
  </span>
25
154
  </div>
26
155
  <Divider />
27
- <div style={{ fontSize: '0.9rem' }}>
28
- <TypeAndKeyEditor {...props} />
29
- <Divider />
30
- <PositionAndSizeEditor {...props} />
31
- <Divider />
32
- {activeSchema.type === 'text' && (
33
- <>
34
- <TextPropEditor {...props} />
35
- <Divider />
36
- </>
37
- )}
38
- <ExampleInputEditor {...props} />
156
+ <div
157
+ style={{
158
+ height: size.height - RULER_HEIGHT - RULER_HEIGHT / 2 - 145,
159
+ overflowY: 'auto',
160
+ overflowX: 'hidden',
161
+ }}
162
+ >
163
+ <FormRender
164
+ form={form}
165
+ schema={propPanelSchema}
166
+ widgets={widgets}
167
+ watch={{ '#': handleWatch }}
168
+ locale="en-US"
169
+ />
39
170
  </div>
40
171
  </div>
41
172
  );
@@ -72,7 +72,7 @@ const Item = React.memo(
72
72
  {...listeners}
73
73
  style={{ padding: '0.5rem', background: 'none', border: 'none', display: 'flex' }}
74
74
  >
75
- <object style={{ cursor: 'grab' }} width={15}>
75
+ <object style={{ cursor: 'grab', marginTop: 6 }} width={15}>
76
76
  <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="16" height="16">
77
77
  <path d="M10 13a1 1 0 100-2 1 1 0 000 2zm-4 0a1 1 0 100-2 1 1 0 000 2zm1-5a1 1 0 11-2 0 1 1 0 012 0zm3 1a1 1 0 100-2 1 1 0 000 2zm1-5a1 1 0 11-2 0 1 1 0 012 0zM6 5a1 1 0 100-2 1 1 0 000 2z"></path>
78
78
  </svg>
@@ -16,9 +16,9 @@ import {
16
16
  verticalListSortingStrategy,
17
17
  } from '@dnd-kit/sortable';
18
18
  import { SchemaForUI } from '@pdfme/common';
19
+ import type { SidebarProps } from '../../../../types';
19
20
  import Item from './Item';
20
21
  import SelectableSortableItem from './SelectableSortableItem';
21
- import { SidebarProps } from '../index';
22
22
 
23
23
  const SelectableSortableContainer = (
24
24
  props: Pick<
@@ -58,16 +58,16 @@ const SelectableSortableContainer = (
58
58
  sensors={sensors}
59
59
  collisionDetection={closestCorners}
60
60
  onDragStart={({ active }) => {
61
- setActiveId(active.id);
61
+ setActiveId(String(active.id));
62
62
  setClonedItems(schemas);
63
63
 
64
- if (!isItemSelected(active.id)) {
64
+ if (!isItemSelected(String(active.id))) {
65
65
  const newSelectedSchemas: SchemaForUI[] = [];
66
66
  setSelectedSchemas(newSelectedSchemas);
67
67
  } else if (selectedSchemas.length > 0) {
68
68
  onSortEnd(
69
69
  selectedSchemas.reduce((ret, selectedItem) => {
70
- if (selectedItem.id === active.id) {
70
+ if (selectedItem.id === String(active.id)) {
71
71
  return ret;
72
72
  }
73
73
  return ret.filter((schema) => schema !== selectedItem);
@@ -78,8 +78,8 @@ const SelectableSortableContainer = (
78
78
  onDragEnd={({ active, over }) => {
79
79
  const overId = over?.id || '';
80
80
 
81
- const activeIndex = schemas.map((i) => i.id).indexOf(active.id);
82
- const overIndex = schemas.map((i) => i.id).indexOf(overId);
81
+ const activeIndex = schemas.map((i) => i.id).indexOf(String(active.id));
82
+ const overIndex = schemas.map((i) => i.id).indexOf(String(overId));
83
83
 
84
84
  if (selectedSchemas.length) {
85
85
  let newSchemas = [...schemas];