@pdfme/ui 0.0.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.
Files changed (97) hide show
  1. package/README.md +9 -0
  2. package/__mocks__/assetsTransformer.js +7 -0
  3. package/__mocks__/form-render.js +7 -0
  4. package/__mocks__/lucide-react.js +19 -0
  5. package/dist/index.es.js +159393 -0
  6. package/dist/index.umd.js +1041 -0
  7. package/dist/types/__tests__/assets/helper.d.ts +3 -0
  8. package/dist/types/__tests__/components/Designer.test.d.ts +1 -0
  9. package/dist/types/__tests__/components/PluginIcon.test.d.ts +1 -0
  10. package/dist/types/__tests__/components/Preview.test.d.ts +1 -0
  11. package/dist/types/__tests__/helper.test.d.ts +1 -0
  12. package/dist/types/src/Designer.d.ts +21 -0
  13. package/dist/types/src/Form.d.ts +24 -0
  14. package/dist/types/src/Viewer.d.ts +15 -0
  15. package/dist/types/src/class.d.ts +89 -0
  16. package/dist/types/src/components/AppContextProvider.d.ts +11 -0
  17. package/dist/types/src/components/CtlBar.d.ts +14 -0
  18. package/dist/types/src/components/Designer/Canvas/Guides.d.ts +9 -0
  19. package/dist/types/src/components/Designer/Canvas/Mask.d.ts +4 -0
  20. package/dist/types/src/components/Designer/Canvas/Moveable.d.ts +37 -0
  21. package/dist/types/src/components/Designer/Canvas/Padding.d.ts +6 -0
  22. package/dist/types/src/components/Designer/Canvas/Selecto.d.ts +10 -0
  23. package/dist/types/src/components/Designer/Canvas/index.d.ts +22 -0
  24. package/dist/types/src/components/Designer/LeftSidebar.d.ts +8 -0
  25. package/dist/types/src/components/Designer/PluginIcon.d.ts +10 -0
  26. package/dist/types/src/components/Designer/RightSidebar/DetailView/AlignWidget.d.ts +4 -0
  27. package/dist/types/src/components/Designer/RightSidebar/DetailView/ButtonGroupWidget.d.ts +4 -0
  28. package/dist/types/src/components/Designer/RightSidebar/DetailView/WidgetRenderer.d.ts +7 -0
  29. package/dist/types/src/components/Designer/RightSidebar/DetailView/index.d.ts +8 -0
  30. package/dist/types/src/components/Designer/RightSidebar/ListView/Item.d.ts +45 -0
  31. package/dist/types/src/components/Designer/RightSidebar/ListView/SelectableSortableContainer.d.ts +4 -0
  32. package/dist/types/src/components/Designer/RightSidebar/ListView/SelectableSortableItem.d.ts +14 -0
  33. package/dist/types/src/components/Designer/RightSidebar/ListView/index.d.ts +4 -0
  34. package/dist/types/src/components/Designer/RightSidebar/index.d.ts +4 -0
  35. package/dist/types/src/components/Designer/RightSidebar/layout.d.ts +15 -0
  36. package/dist/types/src/components/Designer/index.d.ts +11 -0
  37. package/dist/types/src/components/ErrorScreen.d.ts +7 -0
  38. package/dist/types/src/components/Paper.d.ts +20 -0
  39. package/dist/types/src/components/Preview.d.ts +15 -0
  40. package/dist/types/src/components/Renderer.d.ts +13 -0
  41. package/dist/types/src/components/Root.d.ts +9 -0
  42. package/dist/types/src/components/Spinner.d.ts +3 -0
  43. package/dist/types/src/components/StaticSchema.d.ts +10 -0
  44. package/dist/types/src/components/UnitPager.d.ts +10 -0
  45. package/dist/types/src/constants.d.ts +11 -0
  46. package/dist/types/src/contexts.d.ts +10 -0
  47. package/dist/types/src/helper.d.ts +73 -0
  48. package/dist/types/src/hooks.d.ts +46 -0
  49. package/dist/types/src/i18n.d.ts +3 -0
  50. package/dist/types/src/index.d.ts +4 -0
  51. package/dist/types/src/theme.d.ts +2 -0
  52. package/dist/types/src/types.d.ts +19 -0
  53. package/eslint.config.mjs +41 -0
  54. package/package.json +127 -0
  55. package/src/Designer.tsx +107 -0
  56. package/src/Form.tsx +102 -0
  57. package/src/Viewer.tsx +59 -0
  58. package/src/class.ts +188 -0
  59. package/src/components/AppContextProvider.tsx +78 -0
  60. package/src/components/CtlBar.tsx +183 -0
  61. package/src/components/Designer/Canvas/Guides.tsx +49 -0
  62. package/src/components/Designer/Canvas/Mask.tsx +20 -0
  63. package/src/components/Designer/Canvas/Moveable.tsx +91 -0
  64. package/src/components/Designer/Canvas/Padding.tsx +56 -0
  65. package/src/components/Designer/Canvas/Selecto.tsx +45 -0
  66. package/src/components/Designer/Canvas/index.tsx +536 -0
  67. package/src/components/Designer/LeftSidebar.tsx +120 -0
  68. package/src/components/Designer/PluginIcon.tsx +87 -0
  69. package/src/components/Designer/RightSidebar/DetailView/AlignWidget.tsx +229 -0
  70. package/src/components/Designer/RightSidebar/DetailView/ButtonGroupWidget.tsx +78 -0
  71. package/src/components/Designer/RightSidebar/DetailView/WidgetRenderer.tsx +28 -0
  72. package/src/components/Designer/RightSidebar/DetailView/index.tsx +469 -0
  73. package/src/components/Designer/RightSidebar/ListView/Item.tsx +158 -0
  74. package/src/components/Designer/RightSidebar/ListView/SelectableSortableContainer.tsx +204 -0
  75. package/src/components/Designer/RightSidebar/ListView/SelectableSortableItem.tsx +88 -0
  76. package/src/components/Designer/RightSidebar/ListView/index.tsx +116 -0
  77. package/src/components/Designer/RightSidebar/index.tsx +72 -0
  78. package/src/components/Designer/RightSidebar/layout.tsx +75 -0
  79. package/src/components/Designer/index.tsx +389 -0
  80. package/src/components/ErrorScreen.tsx +33 -0
  81. package/src/components/Paper.tsx +117 -0
  82. package/src/components/Preview.tsx +220 -0
  83. package/src/components/Renderer.tsx +165 -0
  84. package/src/components/Root.tsx +38 -0
  85. package/src/components/Spinner.tsx +45 -0
  86. package/src/components/StaticSchema.tsx +50 -0
  87. package/src/components/UnitPager.tsx +119 -0
  88. package/src/constants.ts +21 -0
  89. package/src/contexts.ts +14 -0
  90. package/src/helper.ts +534 -0
  91. package/src/hooks.ts +308 -0
  92. package/src/i18n.ts +903 -0
  93. package/src/index.ts +5 -0
  94. package/src/theme.ts +20 -0
  95. package/src/types.ts +20 -0
  96. package/tsconfig.json +48 -0
  97. package/vite.config.mts +22 -0
@@ -0,0 +1,87 @@
1
+ import React, { useContext, useMemo } from 'react';
2
+ import { Plugin, Schema } from '@pdfme/common';
3
+ import { OptionsContext } from '../../contexts.js';
4
+ import { theme } from 'antd';
5
+ import DOMPurify from 'dompurify';
6
+
7
+ interface PluginIconProps {
8
+ plugin: Plugin<Schema>;
9
+ label: string;
10
+ size?: number;
11
+ styles?: React.CSSProperties;
12
+ }
13
+
14
+ const SVGIcon = ({ svgString, size, styles, label }: {
15
+ svgString: string;
16
+ size?: number;
17
+ styles?: React.CSSProperties;
18
+ label: string;
19
+ }) => {
20
+ const processedSVG = useMemo(() => {
21
+ // First sanitize the SVG string using DOMPurify with SVG profile
22
+ const sanitizedSVG = DOMPurify.sanitize(svgString, {
23
+ USE_PROFILES: { svg: true, svgFilters: true },
24
+ ALLOWED_TAGS: ['svg', 'path', 'circle', 'rect', 'line', 'polygon', 'polyline', 'ellipse', 'g', 'defs', 'title', 'desc', 'metadata'],
25
+ ALLOWED_ATTR: ['class', 'id', 'fill', 'stroke', 'stroke-width', 'viewBox', 'width', 'height', 'd', 'cx', 'cy', 'r', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'points', 'rx', 'ry', 'transform'],
26
+ FORBID_TAGS: ['script', 'foreignObject', 'use', 'embed', 'iframe', 'object', 'link', 'style'],
27
+ FORBID_ATTR: ['onload', 'onerror', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'href', 'xlink:href', 'src', 'action', 'formaction'],
28
+ KEEP_CONTENT: false
29
+ });
30
+
31
+ const parser = new DOMParser();
32
+ const doc = parser.parseFromString(sanitizedSVG, 'image/svg+xml');
33
+
34
+ const svgElement = doc.querySelector('svg');
35
+ if (!svgElement) {
36
+ return null;
37
+ }
38
+
39
+ // Apply size attributes if specified
40
+ if (size) {
41
+ svgElement.setAttribute('width', size.toString());
42
+ svgElement.setAttribute('height', size.toString());
43
+ }
44
+
45
+ return svgElement.outerHTML;
46
+ }, [svgString, size]);
47
+
48
+ if (!processedSVG) {
49
+ return null;
50
+ }
51
+
52
+ return (
53
+ <div
54
+ style={styles}
55
+ title={label}
56
+ dangerouslySetInnerHTML={{ __html: processedSVG }}
57
+ />
58
+ );
59
+ };
60
+
61
+ const PluginIcon = (props: PluginIconProps) => {
62
+ const { plugin, label, size, styles } = props;
63
+ const { token } = theme.useToken();
64
+ const options = useContext(OptionsContext);
65
+
66
+ const schemaType = plugin.propPanel.defaultSchema?.type ?? '';
67
+
68
+ const icon = options.icons?.[schemaType] ?? plugin.icon;
69
+ const iconStyles = {
70
+ ...styles,
71
+ color: token.colorText,
72
+ display: 'flex',
73
+ justifyContent: 'center',
74
+ };
75
+
76
+ if (icon) {
77
+ return <SVGIcon svgString={icon} size={size} styles={iconStyles} label={label} />;
78
+ }
79
+
80
+ return (
81
+ <div style={{ ...styles, overflow: 'hidden', fontSize: 10 }} title={label}>
82
+ {label}
83
+ </div>
84
+ );
85
+ };
86
+
87
+ export default PluginIcon;
@@ -0,0 +1,229 @@
1
+ import { Space, Button, Form } from 'antd';
2
+ import React from 'react';
3
+ import type { PropPanelWidgetProps } from '@pdfme/common';
4
+ import { DESIGNER_CLASSNAME } from '../../../../constants.js';
5
+ import {
6
+ AlignStartVertical,
7
+ AlignStartHorizontal,
8
+ AlignCenterVertical,
9
+ AlignCenterHorizontal,
10
+ AlignEndVertical,
11
+ AlignEndHorizontal,
12
+ AlignVerticalSpaceAround,
13
+ AlignHorizontalSpaceAround,
14
+ } from 'lucide-react';
15
+ import { round } from '../../../../helper.js';
16
+
17
+ const AlignWidget = (props: PropPanelWidgetProps) => {
18
+ const { activeElements, changeSchemas, schemas, pageSize, schema } = props;
19
+ const align = (type: 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom') => {
20
+ const ids = activeElements.map((ae) => ae.id);
21
+ const ass = schemas.filter((s) => ids.includes(s.id));
22
+
23
+ const isVertical = ['left', 'center', 'right'].includes(type);
24
+ const tgtPos = isVertical ? 'x' : 'y';
25
+ const tgtSize = isVertical ? 'width' : 'height';
26
+ const isSingle = ass.length === 1;
27
+ // Access pageSize property safely with proper type assertion
28
+ const root =
29
+ pageSize && typeof pageSize === 'object'
30
+ ? tgtSize === 'width'
31
+ ? (pageSize as unknown as { width: number }).width
32
+ : (pageSize as unknown as { height: number }).height
33
+ : 0;
34
+
35
+ // Access position properties safely with proper type assertion
36
+ const min = isSingle
37
+ ? 0
38
+ : Math.min(
39
+ ...ass.map((as) => {
40
+ // Safely access position property with proper type assertion
41
+ const position =
42
+ as.position && typeof as.position === 'object'
43
+ ? (as.position as unknown as { x: number; y: number })
44
+ : { x: 0, y: 0 };
45
+ return tgtPos === 'x' ? position.x : position.y;
46
+ }),
47
+ );
48
+ const max = isSingle
49
+ ? root
50
+ : Math.max(
51
+ ...ass.map((as) => {
52
+ // Safely access position and size properties with proper type assertion
53
+ const position =
54
+ as.position && typeof as.position === 'object'
55
+ ? (as.position as unknown as { x: number; y: number })
56
+ : { x: 0, y: 0 };
57
+ const posValue = tgtPos === 'x' ? position.x : position.y;
58
+
59
+ // Safely access width/height with proper type assertion
60
+ const asWithSize = as as unknown as { width?: number; height?: number };
61
+ const sizeValue = tgtSize === 'width' ? asWithSize.width || 0 : asWithSize.height || 0;
62
+
63
+ return posValue + sizeValue;
64
+ }),
65
+ );
66
+
67
+ let basePos = min;
68
+ // Define adjust function with consistent parameter usage
69
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
70
+ let adjust = (_size: number): number => 0;
71
+
72
+ if (['center', 'middle'].includes(type)) {
73
+ basePos = (min + max) / 2;
74
+ adjust = (size: number): number => size / 2;
75
+ } else if (['right', 'bottom'].includes(type)) {
76
+ basePos = max;
77
+ adjust = (size: number): number => size;
78
+ }
79
+
80
+ changeSchemas(
81
+ ass.map((as) => {
82
+ // Safely access size property with proper type assertion
83
+ const asWithSize = as as unknown as { width?: number; height?: number; id: string };
84
+ const sizeValue = tgtSize === 'width' ? asWithSize.width || 0 : asWithSize.height || 0;
85
+
86
+ return {
87
+ key: `position.${tgtPos}`,
88
+ value: round(basePos - adjust(sizeValue), 2),
89
+ schemaId: asWithSize.id,
90
+ };
91
+ }),
92
+ );
93
+ };
94
+
95
+ const distribute = (type: 'vertical' | 'horizontal') => {
96
+ const ids = activeElements.map((ae) => ae.id);
97
+ const ass = schemas.filter((s) => ids.includes(s.id));
98
+
99
+ const isVertical = type === 'vertical';
100
+ const tgtPos = isVertical ? 'y' : 'x';
101
+ const tgtSize = isVertical ? 'height' : 'width';
102
+
103
+ // Safely access position property with proper type assertion
104
+ const min = Math.min(
105
+ ...ass.map((as) => {
106
+ const position =
107
+ as.position && typeof as.position === 'object'
108
+ ? (as.position as unknown as { x: number; y: number })
109
+ : { x: 0, y: 0 };
110
+ return tgtPos === 'x' ? position.x : position.y;
111
+ }),
112
+ );
113
+
114
+ // Safely access position and size properties with proper type assertion
115
+ const max = Math.max(
116
+ ...ass.map((as) => {
117
+ const position =
118
+ as.position && typeof as.position === 'object'
119
+ ? (as.position as unknown as { x: number; y: number })
120
+ : { x: 0, y: 0 };
121
+ const posValue = tgtPos === 'x' ? position.x : position.y;
122
+
123
+ // Safely access width/height with proper type assertion
124
+ const asWithSize = as as unknown as { width?: number; height?: number };
125
+ const sizeValue = tgtSize === 'width' ? asWithSize.width || 0 : asWithSize.height || 0;
126
+
127
+ return posValue + sizeValue;
128
+ }),
129
+ );
130
+
131
+ if (ass.length < 3) return;
132
+
133
+ const boxPos = min;
134
+ const boxSize = max - min;
135
+ // Safely access size property with proper type assertion
136
+ const sum = ass.reduce((acc, cur) => {
137
+ const curWithSize = cur as unknown as { width?: number; height?: number };
138
+ const sizeValue = tgtSize === 'width' ? curWithSize.width || 0 : curWithSize.height || 0;
139
+ return acc + sizeValue;
140
+ }, 0);
141
+ const remain = boxSize - sum;
142
+ const unit = remain / (ass.length - 1);
143
+
144
+ let prev = 0;
145
+ changeSchemas(
146
+ ass.map((as, index) => {
147
+ // Safely access size property of previous element with proper type assertion
148
+ const prevSize =
149
+ index === 0
150
+ ? 0
151
+ : (() => {
152
+ const prevAs = ass[index - 1] as unknown as { width?: number; height?: number };
153
+ return tgtSize === 'width' ? prevAs.width || 0 : prevAs.height || 0;
154
+ })();
155
+
156
+ prev += index === 0 ? 0 : prevSize + unit;
157
+ const value = round(boxPos + prev, 2);
158
+
159
+ // Safely access id with proper type assertion
160
+ const asWithId = as as unknown as { id: string };
161
+ return { key: `position.${tgtPos}`, value, schemaId: asWithId.id };
162
+ }),
163
+ );
164
+ };
165
+ const layoutBtns: {
166
+ id: string;
167
+ icon: React.JSX.Element;
168
+ onClick: () => void;
169
+ }[] = [
170
+ {
171
+ id: 'left',
172
+ icon: <AlignStartVertical size={15} />,
173
+ onClick: () => align('left'),
174
+ },
175
+ {
176
+ id: 'center',
177
+ icon: <AlignCenterVertical size={15} />,
178
+ onClick: () => align('center'),
179
+ },
180
+ {
181
+ id: 'right',
182
+ icon: <AlignEndVertical size={15} />,
183
+ onClick: () => align('right'),
184
+ },
185
+ {
186
+ id: 'top',
187
+ icon: <AlignStartHorizontal size={15} />,
188
+ onClick: () => align('top'),
189
+ },
190
+ {
191
+ id: 'middle',
192
+ icon: <AlignCenterHorizontal size={15} />,
193
+ onClick: () => align('middle'),
194
+ },
195
+ {
196
+ id: 'bottom',
197
+ icon: <AlignEndHorizontal size={15} />,
198
+ onClick: () => align('bottom'),
199
+ },
200
+ {
201
+ id: 'vertical',
202
+ icon: <AlignVerticalSpaceAround size={15} />,
203
+ onClick: () => distribute('vertical'),
204
+ },
205
+ {
206
+ id: 'horizontal',
207
+ icon: <AlignHorizontalSpaceAround size={15} />,
208
+ onClick: () => distribute('horizontal'),
209
+ },
210
+ ];
211
+
212
+ return (
213
+ <Form.Item label={schema.title}>
214
+ <Space.Compact>
215
+ {layoutBtns.map((btn) => (
216
+ <Button
217
+ className={DESIGNER_CLASSNAME + 'align-' + btn.id}
218
+ key={btn.id}
219
+ style={{ padding: 7 }}
220
+ disabled={activeElements.length <= 2 && ['vertical', 'horizontal'].includes(btn.id)}
221
+ {...btn}
222
+ />
223
+ ))}
224
+ </Space.Compact>
225
+ </Form.Item>
226
+ );
227
+ };
228
+
229
+ export default AlignWidget;
@@ -0,0 +1,78 @@
1
+ import { Space, Button, Form, theme } from 'antd';
2
+ import React from 'react';
3
+ import type { PropPanelWidgetProps, SchemaForUI } from '@pdfme/common';
4
+ interface ButtonConfig {
5
+ key: string;
6
+ icon: string;
7
+ type: 'boolean' | 'select';
8
+ value?: string;
9
+ }
10
+
11
+ const ButtonGroupWidget = (props: PropPanelWidgetProps) => {
12
+ const { activeElements, changeSchemas, schemas, schema } = props;
13
+ const { token } = theme.useToken();
14
+
15
+ const apply = (btn: ButtonConfig) => {
16
+ const key = btn.key;
17
+ const type = btn.type;
18
+ const ids = activeElements.map((ae) => ae.id);
19
+ const ass = schemas.filter((s) => ids.includes(s.id));
20
+ changeSchemas(
21
+ ass.map((s: SchemaForUI) => {
22
+ const oldValue = Boolean((s as Record<string, unknown>)[key] ?? false);
23
+ const newValue = type === 'boolean' ? !oldValue : btn.value;
24
+ return { key, value: newValue, schemaId: s.id };
25
+ }),
26
+ );
27
+ };
28
+
29
+ const isActive = (btn: ButtonConfig) => {
30
+ const key = btn.key;
31
+ const type = btn.type;
32
+ let active = false;
33
+ const ids = activeElements.map((ae) => ae.id);
34
+ const ass = schemas.filter((s) => ids.includes(s.id));
35
+ ass.forEach((s: SchemaForUI) => {
36
+ // Cast schema to Record to safely access dynamic properties
37
+ const schemaRecord = s as Record<string, unknown>;
38
+ active =
39
+ type === 'boolean' ? Boolean(schemaRecord[key] ?? false) : schemaRecord[key] === btn.value;
40
+ });
41
+ return active;
42
+ };
43
+
44
+ const replaceCurrentColor = (svgString: string, color?: string) =>
45
+ color ? svgString.replace(/="currentColor"/g, `="${color}"`) : svgString;
46
+
47
+ const svgIcon = (svgString: string) => {
48
+ const svgDataUrl = `data:image/svg+xml;utf8,${encodeURIComponent(
49
+ replaceCurrentColor(svgString, token.colorText),
50
+ )}`;
51
+ return <img width={17} height={17} src={svgDataUrl} alt="" />;
52
+ };
53
+
54
+ return (
55
+ <Form.Item>
56
+ <Space.Compact>
57
+ {(schema.buttons as ButtonConfig[]).map((btn: ButtonConfig, index: number) => {
58
+ const active = isActive(btn);
59
+ return (
60
+ <Button
61
+ type={active ? 'primary' : undefined}
62
+ ghost={active}
63
+ onClick={() => apply(btn)}
64
+ style={{
65
+ padding: 7,
66
+ zIndex: active ? 2 : 0,
67
+ }}
68
+ key={index}
69
+ icon={svgIcon(btn.icon)}
70
+ />
71
+ );
72
+ })}
73
+ </Space.Compact>
74
+ </Form.Item>
75
+ );
76
+ };
77
+
78
+ export default ButtonGroupWidget;
@@ -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;