@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
package/src/class.ts ADDED
@@ -0,0 +1,188 @@
1
+ import ReactDOM from 'react-dom';
2
+ import { DESTROYED_ERR_MSG, DEFAULT_LANG } from './constants.js';
3
+ import { debounce } from './helper.js';
4
+ import {
5
+ cloneDeep,
6
+ Template,
7
+ Size,
8
+ Lang,
9
+ Font,
10
+ UIProps,
11
+ UIOptions,
12
+ PluginRegistry,
13
+ PreviewProps,
14
+ getDefaultFont,
15
+ checkUIProps,
16
+ checkTemplate,
17
+ checkInputs,
18
+ checkUIOptions,
19
+ checkPreviewProps,
20
+ pluginRegistry,
21
+ } from '@pdfme/common';
22
+ import { builtInPlugins } from '@pdfme/schemas';
23
+
24
+ export abstract class BaseUIClass {
25
+ protected domContainer!: HTMLElement | null;
26
+
27
+ protected template!: Template;
28
+
29
+ protected size!: Size;
30
+
31
+ private lang: Lang = DEFAULT_LANG;
32
+
33
+ private font: Font = getDefaultFont();
34
+
35
+ private pluginsRegistry: PluginRegistry = pluginRegistry(builtInPlugins);
36
+
37
+ private options: UIOptions = {};
38
+
39
+ private readonly setSize = debounce(() => {
40
+ if (!this.domContainer) {
41
+ return;
42
+ }
43
+
44
+ const rect = this.domContainer.getBoundingClientRect();
45
+ const vw = window.innerWidth;
46
+ const vh = window.innerHeight;
47
+
48
+ const visibleWidth = Math.max(0, Math.min(rect.right, vw) - Math.max(rect.left, 0));
49
+ const visibleHeight = Math.max(0, Math.min(rect.bottom, vh) - Math.max(rect.top, 0));
50
+
51
+ this.size = {
52
+ height: visibleHeight,
53
+ width: visibleWidth,
54
+ };
55
+
56
+ this.render();
57
+ }, 100);
58
+
59
+ resizeObserver = new ResizeObserver(this.setSize);
60
+
61
+ constructor(props: UIProps) {
62
+ checkUIProps(props);
63
+
64
+ const { domContainer, template, options = {}, plugins = {} } = props;
65
+ this.domContainer = domContainer;
66
+ this.template = cloneDeep(template);
67
+ this.options = options;
68
+ this.size = {
69
+ height: this.domContainer.clientHeight || window.innerHeight,
70
+ width: this.domContainer.clientWidth || window.innerWidth,
71
+ };
72
+ this.resizeObserver.observe(this.domContainer);
73
+
74
+ const { lang, font } = options;
75
+ if (lang) {
76
+ this.lang = lang;
77
+ }
78
+ if (font) {
79
+ this.font = font;
80
+ }
81
+
82
+ if (Object.values(plugins).length > 0) {
83
+ this.pluginsRegistry = pluginRegistry(plugins);
84
+ }
85
+ }
86
+
87
+ protected getLang() {
88
+ return this.lang;
89
+ }
90
+
91
+ protected getFont() {
92
+ return this.font;
93
+ }
94
+
95
+ protected getPluginsRegistry() {
96
+ return this.pluginsRegistry;
97
+ }
98
+
99
+ public getOptions() {
100
+ return this.options;
101
+ }
102
+
103
+ public getTemplate() {
104
+ if (!this.domContainer) throw Error(DESTROYED_ERR_MSG);
105
+
106
+ return this.template;
107
+ }
108
+
109
+ public updateTemplate(template: Template) {
110
+ checkTemplate(template);
111
+ if (!this.domContainer) throw Error(DESTROYED_ERR_MSG);
112
+
113
+ this.template = cloneDeep(template);
114
+ this.render();
115
+ }
116
+
117
+ public updateOptions(options: UIOptions) {
118
+ checkUIOptions(options);
119
+ const { lang, font } = options || {};
120
+
121
+ if (lang) {
122
+ this.lang = lang;
123
+ }
124
+ if (font) {
125
+ this.font = font;
126
+ }
127
+ this.options = Object.assign(this.options, options);
128
+ this.render();
129
+ }
130
+
131
+ public destroy() {
132
+ if (!this.domContainer) throw Error(DESTROYED_ERR_MSG);
133
+ ReactDOM.unmountComponentAtNode(this.domContainer);
134
+
135
+ this.resizeObserver.unobserve(this.domContainer);
136
+ this.domContainer = null;
137
+ }
138
+
139
+ protected abstract render(): void;
140
+ }
141
+ export abstract class PreviewUI extends BaseUIClass {
142
+ protected inputs!: { [key: string]: string }[];
143
+
144
+ constructor(props: PreviewProps) {
145
+ super(props);
146
+ checkPreviewProps(props);
147
+ this.inputs = convertToStingObjectArray(cloneDeep(props.inputs));
148
+ }
149
+
150
+ public getInputs() {
151
+ if (!this.domContainer) throw Error(DESTROYED_ERR_MSG);
152
+
153
+ return this.inputs;
154
+ }
155
+
156
+ public setInputs(inputs: { [key: string]: string }[]) {
157
+ if (!this.domContainer) throw Error(DESTROYED_ERR_MSG);
158
+ checkInputs(inputs);
159
+
160
+ this.inputs = convertToStingObjectArray(inputs);
161
+ this.render();
162
+ }
163
+
164
+ protected abstract render(): void;
165
+ }
166
+
167
+ type DataItem = {
168
+ [key: string]: string | string[][];
169
+ };
170
+
171
+ type StringifiedDataItem = {
172
+ [key: string]: string;
173
+ };
174
+
175
+ function convertToStingObjectArray(data: DataItem[]): StringifiedDataItem[] {
176
+ return data.map((item) => {
177
+ const stringifiedItem: StringifiedDataItem = {};
178
+ Object.keys(item).forEach((key) => {
179
+ const value = item[key];
180
+ if (Array.isArray(value)) {
181
+ stringifiedItem[key] = JSON.stringify(value);
182
+ } else {
183
+ stringifiedItem[key] = value;
184
+ }
185
+ });
186
+ return stringifiedItem;
187
+ });
188
+ }
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import { ConfigProvider as ThemeConfigProvider } from 'antd';
3
+ import { I18nContext, FontContext, PluginsRegistry, OptionsContext } from '../contexts.js';
4
+ import { i18n, getDict } from '../i18n.js';
5
+ import { defaultTheme } from '../theme.js';
6
+ import type { Dict, Font, Lang, UIOptions, PluginRegistry } from '@pdfme/common';
7
+
8
+ type Props = {
9
+ children: React.ReactNode;
10
+ lang: Lang;
11
+ font: Font;
12
+ plugins: PluginRegistry;
13
+ options: UIOptions;
14
+ };
15
+
16
+ const isObject = (item: unknown): item is Record<string, unknown> =>
17
+ Boolean(item) && typeof item === 'object' && !Array.isArray(item);
18
+
19
+ const deepMerge = <T extends Record<string, unknown>, U extends Record<string, unknown>>(
20
+ target: T,
21
+ source: U,
22
+ ): T & U => {
23
+ let output = { ...target } as T & U;
24
+
25
+ if (isObject(target) && isObject(source)) {
26
+ Object.keys(source).forEach((key) => {
27
+ const sourceValue = source[key];
28
+ if (isObject(sourceValue)) {
29
+ if (!(key in target)) {
30
+ Object.assign(output, { [key]: sourceValue });
31
+ } else {
32
+ const targetValue = target[key];
33
+ if (isObject(targetValue)) {
34
+ // Using Record<string, unknown> for recursive type
35
+ (output as Record<string, unknown>)[key] = deepMerge(targetValue, sourceValue);
36
+ } else {
37
+ Object.assign(output, { [key]: sourceValue });
38
+ }
39
+ }
40
+ } else {
41
+ Object.assign(output, { [key]: sourceValue });
42
+ }
43
+ });
44
+ }
45
+ return output;
46
+ };
47
+
48
+ const AppContextProvider = ({ children, lang, font, plugins, options }: Props) => {
49
+ let theme = defaultTheme;
50
+ if (options.theme) {
51
+ theme = deepMerge(
52
+ theme as unknown as Record<string, unknown>,
53
+ options.theme as unknown as Record<string, unknown>,
54
+ ) as typeof theme;
55
+ }
56
+
57
+ let dict = getDict(lang);
58
+ if (options.labels) {
59
+ dict = deepMerge(
60
+ dict as unknown as Record<string, unknown>,
61
+ options.labels as unknown as Record<string, unknown>,
62
+ ) as typeof dict;
63
+ }
64
+
65
+ return (
66
+ <ThemeConfigProvider theme={theme}>
67
+ <I18nContext.Provider value={(key: keyof Dict) => i18n(key, dict)}>
68
+ <FontContext.Provider value={font}>
69
+ <PluginsRegistry.Provider value={plugins}>
70
+ <OptionsContext.Provider value={options}>{children}</OptionsContext.Provider>
71
+ </PluginsRegistry.Provider>
72
+ </FontContext.Provider>
73
+ </I18nContext.Provider>
74
+ </ThemeConfigProvider>
75
+ );
76
+ };
77
+
78
+ export default AppContextProvider;
@@ -0,0 +1,183 @@
1
+ import React, { useContext } from 'react';
2
+ import { Size } from '@pdfme/common';
3
+ // Import icons from lucide-react
4
+ // Note: In tests, these will be mocked by the mock file in __mocks__/lucide-react.js
5
+ import { Plus, Minus, ChevronLeft, ChevronRight, Ellipsis } from 'lucide-react';
6
+
7
+ import type { MenuProps } from 'antd';
8
+ import { theme, Typography, Button, Dropdown } from 'antd';
9
+ import { I18nContext } from '../contexts.js';
10
+ import { useMaxZoom } from '../helper.js';
11
+ import { UI_CLASSNAME } from '../constants.js';
12
+
13
+ const { Text } = Typography;
14
+
15
+ type TextStyle = { color: string; fontSize: number; margin: number };
16
+ type ZoomProps = {
17
+ zoomLevel: number;
18
+ setZoomLevel: (zoom: number) => void;
19
+ style: { textStyle: TextStyle };
20
+ };
21
+
22
+ const Zoom = ({ zoomLevel, setZoomLevel, style }: ZoomProps) => {
23
+ const zoomStep = 0.25;
24
+ const maxZoom = useMaxZoom();
25
+ const minZoom = 0.25;
26
+
27
+ const nextZoomOut = zoomLevel - zoomStep;
28
+ const nextZoomIn = zoomLevel + zoomStep;
29
+
30
+ return (
31
+ <div style={{ display: 'flex', alignItems: 'center' }}>
32
+ <Button
33
+ className={UI_CLASSNAME + 'zoom-out'}
34
+ type="text"
35
+ disabled={minZoom >= nextZoomOut}
36
+ onClick={() => setZoomLevel(nextZoomOut)}
37
+ icon={<Minus size={16} color={style.textStyle.color} />}
38
+ />
39
+ <Text strong style={style.textStyle}>
40
+ {Math.round(zoomLevel * 100)}%
41
+ </Text>
42
+ <Button
43
+ className={UI_CLASSNAME + 'zoom-in'}
44
+ type="text"
45
+ disabled={maxZoom < nextZoomIn}
46
+ onClick={() => setZoomLevel(nextZoomIn)}
47
+ icon={<Plus size={16} color={style.textStyle.color} />}
48
+ />
49
+ </div>
50
+ );
51
+ };
52
+
53
+ type PagerProps = {
54
+ pageCursor: number;
55
+ pageNum: number;
56
+ setPageCursor: (page: number) => void;
57
+ style: { textStyle: TextStyle };
58
+ };
59
+
60
+ const Pager = ({ pageCursor, pageNum, setPageCursor, style }: PagerProps) => {
61
+ return (
62
+ <div style={{ display: 'flex', alignItems: 'center' }}>
63
+ <Button className={UI_CLASSNAME + 'page-prev'} type="text" disabled={pageCursor <= 0} onClick={() => setPageCursor(pageCursor - 1)}>
64
+ <ChevronLeft size={16} color={style.textStyle.color} />
65
+ </Button>
66
+ <Text strong style={style.textStyle}>
67
+ {pageCursor + 1}/{pageNum}
68
+ </Text>
69
+ <Button
70
+ className={UI_CLASSNAME + 'page-next'}
71
+ type="text"
72
+ disabled={pageCursor + 1 >= pageNum}
73
+ onClick={() => setPageCursor(pageCursor + 1)}
74
+ >
75
+ <ChevronRight size={16} color={style.textStyle.color} />
76
+ </Button>
77
+ </div>
78
+ );
79
+ };
80
+
81
+ type ContextMenuProps = {
82
+ items: MenuProps['items'];
83
+ style: { textStyle: TextStyle };
84
+ };
85
+ const ContextMenu = ({ items, style }: ContextMenuProps) => (
86
+ <Dropdown menu={{ items }} placement="top" arrow trigger={['click']}>
87
+ <Button className={UI_CLASSNAME + 'context-menu'} type="text">
88
+ <Ellipsis size={16} color={style.textStyle.color} />
89
+ </Button>
90
+ </Dropdown>
91
+ );
92
+
93
+ type CtlBarProps = {
94
+ size: Size;
95
+ pageCursor: number;
96
+ pageNum: number;
97
+ setPageCursor: (page: number) => void;
98
+ zoomLevel: number;
99
+ setZoomLevel: (zoom: number) => void;
100
+ addPageAfter?: () => void;
101
+ removePage?: () => void;
102
+ };
103
+
104
+ const CtlBar = (props: CtlBarProps) => {
105
+ const { token } = theme.useToken();
106
+ const i18n = useContext(I18nContext);
107
+
108
+ const {
109
+ size,
110
+ pageCursor,
111
+ pageNum,
112
+ setPageCursor,
113
+ zoomLevel,
114
+ setZoomLevel,
115
+ addPageAfter,
116
+ removePage,
117
+ } = props;
118
+
119
+ const contextMenuItems: MenuProps['items'] = [];
120
+ if (addPageAfter) {
121
+ contextMenuItems.push({
122
+ key: '1',
123
+ label: <div onClick={addPageAfter}>{i18n('addPageAfter')}</div>,
124
+ });
125
+ }
126
+ if (removePage && pageNum > 1 && pageCursor !== 0) {
127
+ contextMenuItems.push({
128
+ key: '2',
129
+ label: <div onClick={removePage}>{i18n('removePage')}</div>,
130
+ });
131
+ }
132
+
133
+ const barWidth = 300;
134
+ const contextMenuWidth = contextMenuItems.length > 0 ? 50 : 0;
135
+ const width = (pageNum > 1 ? barWidth : barWidth / 2) + contextMenuWidth;
136
+
137
+ const textStyle = {
138
+ color: token.colorWhite,
139
+ fontSize: token.fontSize,
140
+ margin: token.marginXS,
141
+ };
142
+
143
+ return (
144
+ <div style={{ position: 'absolute', top: 'auto', bottom: '6%', width: size.width }}>
145
+ <div
146
+ className={UI_CLASSNAME + 'control-bar'}
147
+ style={{
148
+ display: 'flex',
149
+ alignItems: 'center',
150
+ justifyContent: 'space-evenly',
151
+ position: 'relative',
152
+ zIndex: 1,
153
+ left: `calc(50% - ${width / 2}px)`,
154
+ width,
155
+ height: 40,
156
+ boxSizing: 'border-box',
157
+ padding: token.paddingSM,
158
+ borderRadius: token.borderRadius,
159
+ backgroundColor: token.colorBgMask,
160
+ }}
161
+ >
162
+ {pageNum > 1 && (
163
+ <div className={UI_CLASSNAME + 'pager'}>
164
+ <Pager
165
+ style={{ textStyle }}
166
+ pageCursor={pageCursor}
167
+ pageNum={pageNum}
168
+ setPageCursor={setPageCursor}
169
+ />
170
+ </div>
171
+ )}
172
+ <div className={UI_CLASSNAME + 'zoom'}>
173
+ <Zoom style={{ textStyle }} zoomLevel={zoomLevel} setZoomLevel={setZoomLevel} />
174
+ </div>
175
+ {contextMenuItems.length > 0 && (
176
+ <ContextMenu items={contextMenuItems} style={{ textStyle }} />
177
+ )}
178
+ </div>
179
+ </div>
180
+ );
181
+ };
182
+
183
+ export default CtlBar;
@@ -0,0 +1,49 @@
1
+ import React, { Ref } from 'react';
2
+ import GuidesComponent from '@scena/react-guides';
3
+ import { ZOOM, Size } from '@pdfme/common';
4
+ import { RULER_HEIGHT } from '../../../constants.js';
5
+
6
+ const guideStyle = (
7
+ top: number,
8
+ left: number,
9
+ height: number,
10
+ width: number,
11
+ ): React.CSSProperties => ({
12
+ position: 'absolute',
13
+ top,
14
+ left,
15
+ height,
16
+ width,
17
+ background: '#333333',
18
+ });
19
+
20
+ const _Guides = ({
21
+ paperSize,
22
+ horizontalRef,
23
+ verticalRef,
24
+ }: {
25
+ paperSize: Size;
26
+ horizontalRef: Ref<GuidesComponent> | undefined;
27
+ verticalRef: Ref<GuidesComponent> | undefined;
28
+ }) => (
29
+ <>
30
+ <div
31
+ className="ruler-container"
32
+ style={guideStyle(-RULER_HEIGHT, -RULER_HEIGHT, RULER_HEIGHT, RULER_HEIGHT)}
33
+ />
34
+ <GuidesComponent
35
+ zoom={ZOOM}
36
+ style={guideStyle(-RULER_HEIGHT, 0, RULER_HEIGHT, paperSize.width)}
37
+ type="horizontal"
38
+ ref={horizontalRef}
39
+ />
40
+ <GuidesComponent
41
+ zoom={ZOOM}
42
+ style={guideStyle(0, -RULER_HEIGHT, paperSize.height, RULER_HEIGHT)}
43
+ type="vertical"
44
+ ref={verticalRef}
45
+ />
46
+ </>
47
+ );
48
+
49
+ export default _Guides;
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { Size } from '@pdfme/common';
3
+ import { RULER_HEIGHT } from '../../../constants.js';
4
+ import { theme } from 'antd';
5
+
6
+ const Mask = ({ width, height }: Size) => (
7
+ <div
8
+ style={{
9
+ position: 'absolute',
10
+ top: -RULER_HEIGHT,
11
+ left: -RULER_HEIGHT,
12
+ zIndex: 100,
13
+ width,
14
+ height,
15
+ background: theme.useToken().token.colorBgMask,
16
+ }}
17
+ />
18
+ );
19
+
20
+ export default Mask;
@@ -0,0 +1,91 @@
1
+ import React, { useEffect, forwardRef, Ref, useRef } from 'react';
2
+ import MoveableComponent, {
3
+ OnDrag,
4
+ OnRotate,
5
+ OnRotateEnd,
6
+ OnClick,
7
+ OnResize,
8
+ } from 'react-moveable';
9
+ import { uuid } from '../../../helper.js';
10
+ import { theme } from 'antd';
11
+
12
+ type Props = {
13
+ target: HTMLElement[];
14
+ bounds: { left: number; top: number; bottom: number; right: number };
15
+ horizontalGuidelines: number[];
16
+ verticalGuidelines: number[];
17
+ keepRatio: boolean;
18
+ rotatable: boolean;
19
+ onDrag: ({ target, left, top }: OnDrag) => void;
20
+ onDragEnd: ({ target }: { target: HTMLElement | SVGElement }) => void;
21
+ onDragGroupEnd: ({ targets }: { targets: (HTMLElement | SVGElement)[] }) => void;
22
+ onRotate: ({ target, rotate }: OnRotate) => void;
23
+ onRotateEnd: ({ target }: OnRotateEnd) => void;
24
+ onRotateGroupEnd: ({ targets }: { targets: (HTMLElement | SVGElement)[] }) => void;
25
+ onResize: ({ target, width, height, direction }: OnResize) => void;
26
+ onResizeEnd: ({ target }: { target: HTMLElement | SVGElement }) => void;
27
+ onResizeGroupEnd: ({ targets }: { targets: (HTMLElement | SVGElement)[] }) => void;
28
+ onClick: (e: OnClick) => void;
29
+ };
30
+
31
+ const baseClassName = 'pdfme-moveable';
32
+
33
+ const Moveable = (props: Props, ref: Ref<MoveableComponent>) => {
34
+ const { token } = theme.useToken();
35
+ const instanceId = useRef(uuid());
36
+ const uniqueClassName = `${baseClassName}-${instanceId.current}`;
37
+
38
+ useEffect(() => {
39
+ const containerElement = document.querySelector(`.${uniqueClassName}`);
40
+ const moveableLines = document.querySelectorAll(`.${uniqueClassName} .moveable-line`);
41
+ if (containerElement instanceof HTMLElement) {
42
+ containerElement.style.setProperty('--moveable-color', token.colorPrimary);
43
+ moveableLines.forEach((e) => {
44
+ if (e instanceof HTMLElement) {
45
+ e.style.setProperty('--moveable-color', token.colorPrimary);
46
+ }
47
+ });
48
+ }
49
+ }, [props.target, token.colorPrimary, uniqueClassName]);
50
+
51
+ return (
52
+ <MoveableComponent
53
+ className={uniqueClassName}
54
+ rootContainer={document ? document.body : undefined}
55
+ snappable
56
+ draggable
57
+ rotatable={props.rotatable}
58
+ resizable
59
+ throttleDrag={1}
60
+ throttleRotate={1}
61
+ throttleResize={1}
62
+ ref={ref}
63
+ target={props.target}
64
+ bounds={props.bounds}
65
+ horizontalGuidelines={props.horizontalGuidelines}
66
+ verticalGuidelines={props.verticalGuidelines}
67
+ keepRatio={props.keepRatio}
68
+ onRotate={props.onRotate}
69
+ onRotateEnd={props.onRotateEnd}
70
+ onRotateGroup={({ events }: { events: OnRotate[] }) => {
71
+ events.forEach(props.onRotate);
72
+ }}
73
+ onRotateGroupEnd={props.onRotateGroupEnd}
74
+ onDrag={props.onDrag}
75
+ onDragGroup={({ events }: { events: OnDrag[] }) => {
76
+ events.forEach(props.onDrag);
77
+ }}
78
+ onDragEnd={props.onDragEnd}
79
+ onDragGroupEnd={props.onDragGroupEnd}
80
+ onResize={props.onResize}
81
+ onResizeGroup={({ events }: { events: OnResize[] }) => {
82
+ events.forEach(props.onResize);
83
+ }}
84
+ onResizeEnd={props.onResizeEnd}
85
+ onResizeGroupEnd={props.onResizeGroupEnd}
86
+ onClick={props.onClick}
87
+ />
88
+ );
89
+ };
90
+
91
+ export default forwardRef<MoveableComponent, Props>(Moveable);
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import type * as CSS from 'csstype';
3
+ import { ZOOM, BasePdf, isBlankPdf } from '@pdfme/common';
4
+ import { theme } from 'antd';
5
+
6
+ const getPaddingStyle = (i: number, p: number, color: string): CSS.Properties => {
7
+ const style: CSS.Properties = {
8
+ position: 'absolute',
9
+ background: color,
10
+ opacity: 0.25,
11
+ pointerEvents: 'none',
12
+ };
13
+ switch (i) {
14
+ case 0:
15
+ style.top = 0;
16
+ style.height = `${p * ZOOM}px`;
17
+ style.left = 0;
18
+ style.right = 0;
19
+ break;
20
+ case 1:
21
+ style.right = 0;
22
+ style.width = `${p * ZOOM}px`;
23
+ style.top = 0;
24
+ style.bottom = 0;
25
+ break;
26
+ case 2:
27
+ style.bottom = 0;
28
+ style.height = `${p * ZOOM}px`;
29
+ style.left = 0;
30
+ style.right = 0;
31
+ break;
32
+ case 3:
33
+ style.left = 0;
34
+ style.width = `${p * ZOOM}px`;
35
+ style.top = 0;
36
+ style.bottom = 0;
37
+ break;
38
+ default:
39
+ break;
40
+ }
41
+
42
+ return style;
43
+ };
44
+
45
+ const Padding = ({ basePdf }: { basePdf: BasePdf }) => {
46
+ return (
47
+ <>
48
+ {isBlankPdf(basePdf) &&
49
+ basePdf.padding.map((p, i) => (
50
+ <div key={String(i)} style={getPaddingStyle(i, p, theme.useToken().token.colorError)} />
51
+ ))}
52
+ </>
53
+ );
54
+ };
55
+
56
+ export default Padding;