@pdfme/ui 4.2.3 → 4.2.4-dev.10

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.
@@ -21,7 +21,6 @@ export declare abstract class BaseUIClass {
21
21
  getTemplate(): import("zod").objectOutputType<{
22
22
  schemas: import("zod").ZodArray<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodObject<{
23
23
  type: import("zod").ZodString;
24
- icon: import("zod").ZodOptional<import("zod").ZodString>;
25
24
  content: import("zod").ZodOptional<import("zod").ZodString>;
26
25
  position: import("zod").ZodObject<{
27
26
  x: import("zod").ZodNumber;
@@ -38,9 +37,9 @@ export declare abstract class BaseUIClass {
38
37
  rotate: import("zod").ZodOptional<import("zod").ZodNumber>;
39
38
  opacity: import("zod").ZodOptional<import("zod").ZodNumber>;
40
39
  readOnly: import("zod").ZodOptional<import("zod").ZodBoolean>;
40
+ required: import("zod").ZodOptional<import("zod").ZodBoolean>;
41
41
  }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
42
42
  type: import("zod").ZodString;
43
- icon: import("zod").ZodOptional<import("zod").ZodString>;
44
43
  content: import("zod").ZodOptional<import("zod").ZodString>;
45
44
  position: import("zod").ZodObject<{
46
45
  x: import("zod").ZodNumber;
@@ -57,9 +56,9 @@ export declare abstract class BaseUIClass {
57
56
  rotate: import("zod").ZodOptional<import("zod").ZodNumber>;
58
57
  opacity: import("zod").ZodOptional<import("zod").ZodNumber>;
59
58
  readOnly: import("zod").ZodOptional<import("zod").ZodBoolean>;
59
+ required: import("zod").ZodOptional<import("zod").ZodBoolean>;
60
60
  }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
61
61
  type: import("zod").ZodString;
62
- icon: import("zod").ZodOptional<import("zod").ZodString>;
63
62
  content: import("zod").ZodOptional<import("zod").ZodString>;
64
63
  position: import("zod").ZodObject<{
65
64
  x: import("zod").ZodNumber;
@@ -76,6 +75,7 @@ export declare abstract class BaseUIClass {
76
75
  rotate: import("zod").ZodOptional<import("zod").ZodNumber>;
77
76
  opacity: import("zod").ZodOptional<import("zod").ZodNumber>;
78
77
  readOnly: import("zod").ZodOptional<import("zod").ZodBoolean>;
78
+ required: import("zod").ZodOptional<import("zod").ZodBoolean>;
79
79
  }, import("zod").ZodTypeAny, "passthrough">>>, "many">;
80
80
  basePdf: import("zod").ZodUnion<[import("zod").ZodUnion<[import("zod").ZodString, import("zod").ZodType<ArrayBuffer, import("zod").ZodTypeDef, ArrayBuffer>, import("zod").ZodType<Uint8Array, import("zod").ZodTypeDef, Uint8Array>]>, import("zod").ZodObject<{
81
81
  width: import("zod").ZodNumber;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { Plugin } from "@pdfme/common";
3
+ interface PluginIconProps {
4
+ plugin: Plugin<any>;
5
+ label: string;
6
+ size?: number;
7
+ styles?: React.CSSProperties;
8
+ }
9
+ declare const PluginIcon: (props: PluginIconProps) => React.JSX.Element;
10
+ export default PluginIcon;
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
  import type { SchemaForUI } from '@pdfme/common';
3
3
  import type { SidebarProps } from '../../../../types';
4
- declare const DetailView: (props: Pick<SidebarProps, 'size' | 'schemas' | 'pageSize' | 'changeSchemas' | 'activeElements' | 'deselectSchema'> & {
4
+ type DetailViewProps = Pick<SidebarProps, 'size' | 'schemas' | 'pageSize' | 'changeSchemas' | 'activeElements' | 'deselectSchema'> & {
5
5
  activeSchema: SchemaForUI;
6
- }) => React.JSX.Element;
7
- export default DetailView;
6
+ };
7
+ declare const _default: React.MemoExoticComponent<(props: DetailViewProps) => React.JSX.Element>;
8
+ export default _default;
@@ -2,9 +2,11 @@ import React from 'react';
2
2
  import { DraggableSyntheticListeners } from '@dnd-kit/core';
3
3
  interface Props {
4
4
  value: React.ReactNode;
5
+ icon?: React.ReactNode;
5
6
  style?: React.CSSProperties;
6
7
  status?: 'is-warning' | 'is-danger';
7
8
  title?: string;
9
+ required?: boolean;
8
10
  dragOverlay?: boolean;
9
11
  onClick?: () => void;
10
12
  onMouseEnter?: () => void;
@@ -1,6 +1,6 @@
1
1
  /// <reference types="react" />
2
2
  import { Plugins, UIOptions } from '@pdfme/common';
3
- export declare const I18nContext: import("react").Context<(key: "type" | "width" | "height" | "rotate" | "opacity" | "cancel" | "field" | "fieldName" | "align" | "edit" | "plsInputName" | "fieldMustUniq" | "notUniq" | "noKeyName" | "fieldsList" | "editField" | "errorOccurred" | "errorBulkUpdateFieldName" | "commitBulkUpdateFieldName" | "bulkUpdateFieldName" | "addPageAfter" | "removePage" | "removePageConfirm" | "hexColorPrompt" | "schemas.color" | "schemas.borderWidth" | "schemas.borderColor" | "schemas.backgroundColor" | "schemas.textColor" | "schemas.bgColor" | "schemas.horizontal" | "schemas.vertical" | "schemas.left" | "schemas.center" | "schemas.right" | "schemas.top" | "schemas.middle" | "schemas.bottom" | "schemas.padding" | "schemas.text.fontName" | "schemas.text.size" | "schemas.text.spacing" | "schemas.text.textAlign" | "schemas.text.verticalAlign" | "schemas.text.lineHeight" | "schemas.text.min" | "schemas.text.max" | "schemas.text.fit" | "schemas.text.dynamicFontSize" | "schemas.text.format" | "schemas.mvt.typingInstructions" | "schemas.mvt.sampleField" | "schemas.mvt.variablesSampleData" | "schemas.barcodes.barColor" | "schemas.barcodes.includetext" | "schemas.table.alternateBackgroundColor" | "schemas.table.tableStyle" | "schemas.table.headStyle" | "schemas.table.bodyStyle" | "schemas.table.columnStyle", dict?: {
3
+ export declare const I18nContext: import("react").Context<(key: "type" | "width" | "height" | "rotate" | "opacity" | "required" | "cancel" | "field" | "fieldName" | "align" | "edit" | "plsInputName" | "fieldMustUniq" | "notUniq" | "noKeyName" | "fieldsList" | "editField" | "errorOccurred" | "errorBulkUpdateFieldName" | "commitBulkUpdateFieldName" | "bulkUpdateFieldName" | "addPageAfter" | "removePage" | "removePageConfirm" | "hexColorPrompt" | "schemas.color" | "schemas.borderWidth" | "schemas.borderColor" | "schemas.backgroundColor" | "schemas.textColor" | "schemas.bgColor" | "schemas.horizontal" | "schemas.vertical" | "schemas.left" | "schemas.center" | "schemas.right" | "schemas.top" | "schemas.middle" | "schemas.bottom" | "schemas.padding" | "schemas.text.fontName" | "schemas.text.size" | "schemas.text.spacing" | "schemas.text.textAlign" | "schemas.text.verticalAlign" | "schemas.text.lineHeight" | "schemas.text.min" | "schemas.text.max" | "schemas.text.fit" | "schemas.text.dynamicFontSize" | "schemas.text.format" | "schemas.mvt.typingInstructions" | "schemas.mvt.sampleField" | "schemas.mvt.variablesSampleData" | "schemas.barcodes.barColor" | "schemas.barcodes.includetext" | "schemas.table.alternateBackgroundColor" | "schemas.table.tableStyle" | "schemas.table.headStyle" | "schemas.table.bodyStyle" | "schemas.table.columnStyle", dict?: {
4
4
  cancel: string;
5
5
  field: string;
6
6
  fieldName: string;
@@ -10,6 +10,7 @@ export declare const I18nContext: import("react").Context<(key: "type" | "width"
10
10
  height: string;
11
11
  rotate: string;
12
12
  edit: string;
13
+ required: string;
13
14
  plsInputName: string;
14
15
  fieldMustUniq: string;
15
16
  notUniq: string;
@@ -36,7 +36,7 @@ export declare const template2SchemasList: (_template: Template) => Promise<{
36
36
  key: string;
37
37
  opacity?: number | undefined;
38
38
  rotate?: number | undefined;
39
- icon?: string | undefined;
39
+ required?: boolean | undefined;
40
40
  content?: string | undefined;
41
41
  readOnly?: boolean | undefined;
42
42
  }[][]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pdfme/ui",
3
- "version": "4.2.3",
3
+ "version": "4.2.4-dev.10",
4
4
  "sideEffects": false,
5
5
  "author": "hand-dot",
6
6
  "license": "MIT",
@@ -9,6 +9,7 @@ import { useDraggable } from '@dnd-kit/core';
9
9
  import { CSS } from "@dnd-kit/utilities";
10
10
  import Renderer from '../Renderer';
11
11
  import { PluginsRegistry } from '../../contexts';
12
+ import PluginIcon from "./PluginIcon";
12
13
 
13
14
  const Draggable = (props: { plugin: Plugin<any>, scale: number, basePdf: BasePdf, children: React.ReactNode }) => {
14
15
  const { scale, basePdf, plugin } = props;
@@ -44,7 +45,6 @@ const Draggable = (props: { plugin: Plugin<any>, scale: number, basePdf: BasePdf
44
45
  const LeftSidebar = ({ height, scale, basePdf }: { height: number, scale: number, basePdf: BasePdf }) => {
45
46
  const { token } = theme.useToken();
46
47
  const pluginsRegistry = useContext(PluginsRegistry);
47
-
48
48
  const [isDragging, setIsDragging] = useState(false);
49
49
 
50
50
  useEffect(() => {
@@ -76,22 +76,17 @@ const LeftSidebar = ({ height, scale, basePdf }: { height: number, scale: number
76
76
  >
77
77
  {Object.entries(pluginsRegistry).map(([label, plugin]) => {
78
78
  if (!plugin?.propPanel.defaultSchema) return null;
79
+
79
80
  return <Draggable
80
81
  key={label}
81
82
  scale={scale}
82
83
  basePdf={basePdf}
83
84
  plugin={plugin}>
84
85
  <Button
85
- title={label}
86
- onMouseDown={() => {
87
- setIsDragging(true);
88
- }}
89
- style={{ width: 35, height: 35, marginTop: '0.25rem', padding: '0.25rem' }}>
90
- {plugin.propPanel.defaultSchema.icon ?
91
- <div dangerouslySetInnerHTML={{ __html: plugin.propPanel.defaultSchema.icon }} />
92
- :
93
- <div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>{label}</div>
94
- }
86
+ onMouseDown={() => setIsDragging(true)}
87
+ style={{ width: 35, height: 35, marginTop: '0.25rem', padding: '0.25rem' }}
88
+ >
89
+ <PluginIcon plugin={plugin} label={label} />
95
90
  </Button>
96
91
  </Draggable>
97
92
  })}
@@ -0,0 +1,47 @@
1
+ import React, { useContext } from 'react';
2
+ import { Plugin } from "@pdfme/common";
3
+ import { OptionsContext } from '../../contexts';
4
+
5
+ interface PluginIconProps {
6
+ plugin: Plugin<any>;
7
+ label: string;
8
+ size?: number;
9
+ styles?: React.CSSProperties;
10
+ }
11
+
12
+ const getWithModifiedSize = (htmlString: string, label: string, size: number, styles?: React.CSSProperties) => {
13
+ const parser = new DOMParser();
14
+ const doc = parser.parseFromString(htmlString, 'text/html');
15
+
16
+ const modifyNode = (node: HTMLElement) => {
17
+ if (node.tagName === 'SVG' || node.tagName === 'svg') {
18
+ node.setAttribute('width', size.toString());
19
+ node.setAttribute('height', size.toString());
20
+ }
21
+ Array.from(node.children).forEach(child => modifyNode(child as HTMLElement));
22
+ };
23
+
24
+ Array.from(doc.body.children).forEach(child => modifyNode(child as HTMLElement));
25
+
26
+ return (
27
+ <div style={styles} title={label} dangerouslySetInnerHTML={{ __html: doc.body.innerHTML }} />
28
+ );
29
+ };
30
+
31
+ const PluginIcon = (props: PluginIconProps) => {
32
+ const { plugin, label, size, styles } = props;
33
+ const options = useContext(OptionsContext);
34
+ const icon = options.icons?.[plugin.propPanel.defaultSchema.type] ?? plugin.icon;
35
+ const iconStyles = { ...styles, display: 'flex', justifyContent: 'center' };
36
+
37
+ if (icon) {
38
+ if (size) {
39
+ return getWithModifiedSize(icon, label, size, iconStyles);
40
+ }
41
+ return <div style={iconStyles} title={label} dangerouslySetInnerHTML={{__html: icon}} />
42
+ }
43
+
44
+ return <div style={{...styles, overflow: 'hidden', fontSize: 10, }}>{label}</div>
45
+ };
46
+
47
+ export default PluginIcon;
@@ -6,7 +6,7 @@ interface ButtonConfig {
6
6
  key: string;
7
7
  icon: string;
8
8
  type: 'boolean' | 'select';
9
- value?: String;
9
+ value?: string;
10
10
  }
11
11
 
12
12
  const ButtonGroupWidget = (props: PropPanelWidgetProps) => {
@@ -1,5 +1,5 @@
1
1
  import FormRender, { useForm } from 'form-render';
2
- import React, { useContext, useEffect, useState } from 'react';
2
+ import React, { useContext, useState, useEffect } from 'react';
3
3
  import type { ChangeSchemaItem, Dict, SchemaForUI, PropPanelWidgetProps, PropPanelSchema } from '@pdfme/common';
4
4
  import type { SidebarProps } from '../../../../types';
5
5
  import { MenuOutlined } from '@ant-design/icons';
@@ -13,17 +13,16 @@ import { InternalNamePath, ValidateErrorEntity } from "rc-field-form/es/interfac
13
13
 
14
14
  const { Text } = Typography;
15
15
 
16
- const DetailView = (
17
- props: Pick<
18
- SidebarProps,
19
- 'size' | 'schemas' | 'pageSize' | 'changeSchemas' | 'activeElements' | 'deselectSchema'
20
- > & {
21
- activeSchema: SchemaForUI;
22
- }
23
- ) => {
16
+ type DetailViewProps = Pick<SidebarProps,
17
+ 'size' | 'schemas' | 'pageSize' | 'changeSchemas' | 'activeElements' | 'deselectSchema'
18
+ > & {
19
+ activeSchema: SchemaForUI;
20
+ };
21
+
22
+ const DetailView = (props: DetailViewProps) => {
24
23
  const { token } = theme.useToken();
25
24
 
26
- const { size, changeSchemas, deselectSchema, activeSchema, activeElements } = props;
25
+ const { size, changeSchemas, deselectSchema, activeSchema } = props;
27
26
  const form = useForm();
28
27
 
29
28
  const i18n = useContext(I18nContext);
@@ -58,11 +57,10 @@ const DetailView = (
58
57
  });
59
58
  }
60
59
  setWidgets(newWidgets);
61
- }, [activeSchema, activeElements, pluginsRegistry, JSON.stringify(options)]);
60
+ }, [activeSchema, pluginsRegistry, JSON.stringify(options)]);
62
61
 
63
62
  useEffect(() => {
64
63
  const values: any = { ...activeSchema };
65
-
66
64
  // [position] Change the nested position object into a flat, as a three-column layout is difficult to implement
67
65
  values.x = values.position.x;
68
66
  values.y = values.position.y;
@@ -73,7 +71,8 @@ const DetailView = (
73
71
  }
74
72
 
75
73
  form.setValues(values);
76
- }, [form, activeSchema]);
74
+
75
+ }, [activeSchema, form]);
77
76
 
78
77
  const handleWatch = (formSchema: any) => {
79
78
  const formAndSchemaValuesDiffer = (formValue: any, schemaValue: any): boolean => {
@@ -102,7 +101,7 @@ const DetailView = (
102
101
  // FIXME memo: https://github.com/pdfme/pdfme/pull/367#issuecomment-1857468274
103
102
  if (value === null && ['rotate', 'opacity'].includes(key)) value = undefined;
104
103
 
105
- changes.push({key, value, schemaId: activeSchema.id});
104
+ changes.push({ key, value, schemaId: activeSchema.id });
106
105
  }
107
106
  }
108
107
 
@@ -111,11 +110,11 @@ const DetailView = (
111
110
  form.validateFields()
112
111
  .then(() => changeSchemas(changes))
113
112
  .catch((reason: ValidateErrorEntity) => {
114
- if (reason.errorFields.length) {
113
+ if (reason.errorFields.length) {
115
114
  changes = changes.filter((change: ChangeSchemaItem) => !reason.errorFields.find((field: {
116
- name: InternalNamePath;
117
- errors: string[];
118
- }) => field.name.includes(change.key)
115
+ name: InternalNamePath;
116
+ errors: string[];
117
+ }) => field.name.includes(change.key)
119
118
  ));
120
119
  }
121
120
  if (changes.length) {
@@ -153,7 +152,8 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
153
152
  required: true,
154
153
  span: 12,
155
154
  },
156
- key: { title: i18n('fieldName'), type: 'string', required: true, span: 12 },
155
+ key: { title: i18n('fieldName'), type: 'string', required: true, span: 12, props: { autocomplete: "off"} },
156
+ required: { title: i18n('required'), type: 'boolean', span: 8, hidden: defaultSchema?.readOnly },
157
157
  '-': { type: 'void', widget: 'Divider' },
158
158
  align: { title: i18n('align'), type: 'void', widget: 'AlignWidget' },
159
159
  x: { title: 'X', type: 'number', widget: 'inputNumber', required: true, span: 8, min: 0 },
@@ -254,4 +254,8 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
254
254
  );
255
255
  };
256
256
 
257
- export default DetailView;
257
+ const propsAreUnchanged = (prevProps: DetailViewProps, nextProps: DetailViewProps) => {
258
+ return JSON.stringify(prevProps.activeSchema) == JSON.stringify(nextProps.activeSchema)
259
+ };
260
+
261
+ export default React.memo(DetailView, propsAreUnchanged);
@@ -8,9 +8,11 @@ const { Text } = Typography;
8
8
 
9
9
  interface Props {
10
10
  value: React.ReactNode;
11
+ icon?: React.ReactNode;
11
12
  style?: React.CSSProperties;
12
13
  status?: 'is-warning' | 'is-danger';
13
14
  title?: string;
15
+ required?: boolean;
14
16
  dragOverlay?: boolean;
15
17
  onClick?: () => void;
16
18
  onMouseEnter?: () => void;
@@ -26,9 +28,11 @@ const Item = React.memo(
26
28
  React.forwardRef<HTMLLIElement, Props>(
27
29
  (
28
30
  {
31
+ icon,
29
32
  value,
30
33
  status,
31
34
  title,
35
+ required,
32
36
  style,
33
37
  dragOverlay,
34
38
  onClick,
@@ -93,6 +97,7 @@ const Item = React.memo(
93
97
  }}
94
98
  icon={<HolderOutlined style={{ cursor: 'grab' }} />}
95
99
  />
100
+ {icon}
96
101
  <Text
97
102
  style={{
98
103
  overflow: 'hidden',
@@ -105,12 +110,13 @@ const Item = React.memo(
105
110
  {status === undefined ? (
106
111
  value
107
112
  ) : (
108
- <span style={{ display: 'flex', alignItems: 'center' }}>
113
+ <span>
109
114
  <ExclamationCircleOutlined width={15} style={{ marginRight: '0.5rem' }} />
110
115
  {status === 'is-warning' ? i18n('noKeyName') : value}
111
116
  {status === 'is-danger' ? i18n('notUniq') : ''}
112
117
  </span>
113
118
  )}
119
+ {required && <span style={{ color: 'red', marginLeft: '0.5rem' }}>*</span>}
114
120
  </Text>
115
121
  </div>
116
122
  </li>
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react';
1
+ import React, { useState, useContext, ReactNode } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
3
  import {
4
4
  closestCorners,
@@ -17,9 +17,11 @@ import {
17
17
  } from '@dnd-kit/sortable';
18
18
  import { SchemaForUI } from '@pdfme/common';
19
19
  import type { SidebarProps } from '../../../../types';
20
+ import { PluginsRegistry } from '../../../../contexts';
20
21
  import Item from './Item';
21
22
  import SelectableSortableItem from './SelectableSortableItem';
22
23
  import { theme } from 'antd';
24
+ import PluginIcon from "../../PluginIcon";
23
25
 
24
26
  const SelectableSortableContainer = (
25
27
  props: Pick<
@@ -28,11 +30,11 @@ const SelectableSortableContainer = (
28
30
  >
29
31
  ) => {
30
32
  const { token } = theme.useToken();
31
-
32
33
  const { schemas, onEdit, onSortEnd, hoveringSchemaId, onChangeHoveringSchemaId } = props;
33
34
  const [selectedSchemas, setSelectedSchemas] = useState<SchemaForUI[]>([]);
34
- const [dragOverlaydItems, setClonedItems] = useState<SchemaForUI[] | null>(null);
35
+ const [dragOverlaidItems, setClonedItems] = useState<SchemaForUI[] | null>(null);
35
36
  const [activeId, setActiveId] = useState<string | null>(null);
37
+ const pluginsRegistry = useContext(PluginsRegistry);
36
38
  const sensors = useSensors(
37
39
  useSensor(PointerSensor, { activationConstraint: { distance: 15 } }),
38
40
  useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
@@ -56,6 +58,20 @@ const SelectableSortableContainer = (
56
58
  }
57
59
  };
58
60
 
61
+ const getPluginIcon = (inSchema: string|SchemaForUI): ReactNode => {
62
+ const thisSchema = (typeof inSchema === 'string') ? schemas.find((schema) => schema.id === inSchema) : inSchema;
63
+
64
+ const [pluginLabel, activePlugin] = Object.entries(pluginsRegistry).find(
65
+ ([label, plugin]) => plugin?.propPanel.defaultSchema.type === thisSchema?.type
66
+ )!;
67
+
68
+ if (!activePlugin) {
69
+ return <></>
70
+ }
71
+
72
+ return <PluginIcon plugin={activePlugin} label={pluginLabel} size={20} styles={{marginRight: '0.5rem'}}/>
73
+ };
74
+
59
75
  return (
60
76
  <DndContext
61
77
  sensors={sensors}
@@ -101,8 +117,8 @@ const SelectableSortableContainer = (
101
117
  setActiveId(null);
102
118
  }}
103
119
  onDragCancel={() => {
104
- if (dragOverlaydItems) {
105
- onSortEnd(dragOverlaydItems);
120
+ if (dragOverlaidItems) {
121
+ onSortEnd(dragOverlaidItems);
106
122
  }
107
123
 
108
124
  setActiveId(null);
@@ -139,7 +155,9 @@ const SelectableSortableContainer = (
139
155
  <>
140
156
  <ul style={{ margin: 0, padding: 0, listStyle: 'none' }}>
141
157
  <Item
158
+ icon={getPluginIcon(activeId)}
142
159
  value={schemas.find((schema) => schema.id === activeId)!.key}
160
+ required={schemas.find((schema) => schema.id === activeId)!.required}
143
161
  style={{ background: token.colorPrimary }}
144
162
  dragOverlay
145
163
  />
@@ -149,8 +167,10 @@ const SelectableSortableContainer = (
149
167
  .filter((item) => item.id !== activeId)
150
168
  .map((item) => (
151
169
  <Item
170
+ icon={getPluginIcon(item)}
152
171
  key={item.id}
153
172
  value={item.key}
173
+ required={item.required}
154
174
  style={{ background: token.colorPrimary }}
155
175
  dragOverlay
156
176
  />
@@ -1,10 +1,11 @@
1
1
  import React, { useContext } from 'react';
2
2
  import { useSortable } from '@dnd-kit/sortable';
3
3
  import { SchemaForUI } from '@pdfme/common';
4
- import { I18nContext } from '../../../../contexts';
4
+ import { PluginsRegistry, I18nContext } from '../../../../contexts';
5
5
  import Item from './Item';
6
6
  import { useMountStatus } from '../../../../hooks';
7
7
  import { theme } from 'antd';
8
+ import PluginIcon from "../../PluginIcon";
8
9
 
9
10
  interface Props {
10
11
  isSelected: boolean;
@@ -29,6 +30,7 @@ const SelectableSortableItem = ({
29
30
  const { token } = theme.useToken();
30
31
 
31
32
  const i18n = useContext(I18nContext);
33
+ const pluginsRegistry = useContext(PluginsRegistry);
32
34
  const { setNodeRef, listeners, isDragging, isSorting, transform, transition } = useSortable({
33
35
  id: schema.id,
34
36
  });
@@ -40,6 +42,12 @@ const SelectableSortableItem = ({
40
42
  onClick: (event: any) => onSelect(schema.id, event.shiftKey),
41
43
  };
42
44
 
45
+ const [pluginLabel, thisPlugin] = Object.entries(pluginsRegistry).find(
46
+ ([label, plugin]) => plugin?.propPanel.defaultSchema.type === schema.type
47
+ )!;
48
+
49
+ const iconStyles = { width: 20, marginRight: '0.5rem' };
50
+
43
51
  let status: undefined | 'is-warning' | 'is-danger';
44
52
  if (!schema.key) {
45
53
  status = 'is-warning';
@@ -64,9 +72,11 @@ const SelectableSortableItem = ({
64
72
  onMouseEnter={onMouseEnter}
65
73
  onMouseLeave={onMouseLeave}
66
74
  onClick={() => onEdit(schema.id)}
75
+ icon={thisPlugin && <PluginIcon plugin={thisPlugin} label={pluginLabel} size={20} styles={iconStyles}/>}
67
76
  value={schema.key}
68
77
  status={status}
69
78
  title={title}
79
+ required={schema.required}
70
80
  style={{ ...selectedStyle, ...style }}
71
81
  dragging={isDragging}
72
82
  sorting={isSorting}
@@ -15,10 +15,11 @@ import RightSidebar from './RightSidebar/index';
15
15
  import LeftSidebar from './LeftSidebar';
16
16
  import Canvas from './Canvas/index';
17
17
  import { RULER_HEIGHT, RIGHT_SIDEBAR_WIDTH } from '../../constants';
18
- import { I18nContext, PluginsRegistry } from '../../contexts';
18
+ import { I18nContext, OptionsContext, PluginsRegistry } from '../../contexts';
19
19
  import {
20
20
  schemasList2template,
21
21
  uuid,
22
+ round,
22
23
  cloneDeep,
23
24
  template2SchemasList,
24
25
  getPagesScrollTopByIndex,
@@ -51,7 +52,7 @@ const TemplateEditor = ({
51
52
  onSaveTemplate: (t: Template) => void;
52
53
  onChangeTemplate: (t: Template) => void;
53
54
  } & {
54
- onChangeTemplate: (t: Template) => void
55
+ onChangeTemplate: (t: Template) => void
55
56
  onPageCursorChange: (newPageCursor: number) => void
56
57
  }) => {
57
58
  const past = useRef<SchemaForUI[][]>([]);
@@ -61,6 +62,7 @@ const TemplateEditor = ({
61
62
 
62
63
  const i18n = useContext(I18nContext);
63
64
  const pluginsRegistry = useContext(PluginsRegistry);
65
+ const options = useContext(OptionsContext);
64
66
 
65
67
  const [hoveringSchemaId, setHoveringSchemaId] = useState<string | null>(null);
66
68
  const [activeElements, setActiveElements] = useState<HTMLElement[]>([]);
@@ -170,6 +172,7 @@ const TemplateEditor = ({
170
172
  x: ensureMiddleValue(paddingLeft, defaultSchema.position.x, pageSize.width - paddingRight - defaultSchema.width),
171
173
  y: ensureMiddleValue(paddingTop, defaultSchema.position.y, pageSize.height - paddingBottom - defaultSchema.height),
172
174
  },
175
+ required: defaultSchema.readOnly ? false : options.requiredByDefault || defaultSchema.required || false,
173
176
  } as SchemaForUI;
174
177
 
175
178
  if (defaultSchema.position.y === 0) {
@@ -253,7 +256,7 @@ const TemplateEditor = ({
253
256
  const moveY = (event.delta.y - canvasTopOffsetFromPageCorner) / scale;
254
257
  const moveX = (event.delta.x - canvasLeftOffsetFromPageCorner) / scale;
255
258
 
256
- const position = { x: px2mm(Math.max(0, moveX)), y: px2mm(Math.max(0, moveY)) }
259
+ const position = { x: round(px2mm(Math.max(0, moveX)), 2), y: round(px2mm(Math.max(0, moveY)), 2) }
257
260
 
258
261
  addSchema({ ...(active.data.current as Schema), position });
259
262
  }}
@@ -62,6 +62,16 @@ const Wrapper = ({
62
62
  outline,
63
63
  }}
64
64
  >
65
+ {schema.required &&
66
+ <span style={{
67
+ color: 'red',
68
+ position: 'absolute',
69
+ top: -12,
70
+ left: -12,
71
+ fontSize: 18,
72
+ fontWeight: 700,
73
+ }}>*</span>
74
+ }
65
75
  {children}
66
76
  </div>
67
77
  );
package/src/helper.ts CHANGED
@@ -448,7 +448,7 @@ const handleTypeChange = (
448
448
  pluginsRegistry: Plugins
449
449
  ) => {
450
450
  if (key !== 'type') return;
451
- const keysToKeep = ['id', 'key', 'type', 'position'];
451
+ const keysToKeep = ['id', 'key', 'type', 'position', 'required'];
452
452
  Object.keys(schema).forEach((key) => {
453
453
  if (!keysToKeep.includes(key)) {
454
454
  delete schema[key as keyof typeof schema];
@@ -463,6 +463,9 @@ const handleTypeChange = (
463
463
  (schema as any)[key] = propPanel?.defaultSchema[key];
464
464
  }
465
465
  });
466
+ if (schema.readOnly) {
467
+ schema.required = false;
468
+ }
466
469
  };
467
470
 
468
471
  export const changeSchemas = (args: {
@@ -489,4 +492,4 @@ export const changeSchemas = (args: {
489
492
  return acc;
490
493
  }, cloneDeep(schemas));
491
494
  commitSchemas(newSchemas);
492
- };
495
+ };