@pdfme/ui 5.3.11 → 5.3.12-dev.1

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 (35) hide show
  1. package/dist/index.es.js +9116 -8977
  2. package/dist/index.umd.js +125 -125
  3. package/dist/types/src/components/Designer/Canvas/Guides.d.ts +3 -9
  4. package/dist/types/src/components/Designer/Canvas/Moveable.d.ts +2 -49
  5. package/dist/types/src/components/Designer/Canvas/Selecto.d.ts +5 -16
  6. package/dist/types/src/components/Designer/PluginIcon.d.ts +2 -2
  7. package/dist/types/src/components/Designer/RightSidebar/ListView/Item.d.ts +17 -0
  8. package/dist/types/src/components/Renderer.d.ts +1 -10
  9. package/dist/types/src/constants.d.ts +1 -1
  10. package/dist/types/src/contexts.d.ts +1 -1
  11. package/dist/types/src/helper.d.ts +3 -4
  12. package/package.json +3 -3
  13. package/src/components/AppContextProvider.tsx +23 -10
  14. package/src/components/CtlBar.tsx +2 -2
  15. package/src/components/Designer/Canvas/Guides.tsx +4 -13
  16. package/src/components/Designer/Canvas/Moveable.tsx +21 -76
  17. package/src/components/Designer/Canvas/Selecto.tsx +12 -24
  18. package/src/components/Designer/Canvas/index.tsx +51 -72
  19. package/src/components/Designer/LeftSidebar.tsx +2 -2
  20. package/src/components/Designer/PluginIcon.tsx +15 -3
  21. package/src/components/Designer/RightSidebar/DetailView/AlignWidget.tsx +105 -19
  22. package/src/components/Designer/RightSidebar/DetailView/ButtonGroupWidget.tsx +14 -11
  23. package/src/components/Designer/RightSidebar/DetailView/index.tsx +170 -48
  24. package/src/components/Designer/RightSidebar/ListView/Item.tsx +114 -88
  25. package/src/components/Designer/RightSidebar/ListView/SelectableSortableContainer.tsx +23 -3
  26. package/src/components/Designer/RightSidebar/ListView/SelectableSortableItem.tsx +21 -4
  27. package/src/components/Designer/index.tsx +14 -9
  28. package/src/components/Preview.tsx +5 -5
  29. package/src/components/Renderer.tsx +43 -37
  30. package/src/components/Root.tsx +1 -1
  31. package/src/constants.ts +1 -1
  32. package/src/contexts.ts +1 -1
  33. package/src/helper.ts +136 -38
  34. package/src/types/react-guides.d.ts +0 -22
  35. package/src/types/react-selecto.d.ts +0 -35
@@ -1,11 +1,12 @@
1
1
  import { useForm } from 'form-render';
2
2
  import React, { useRef, useContext, useState, useEffect } from 'react';
3
3
  import type {
4
- ChangeSchemaItem,
5
4
  Dict,
5
+ ChangeSchemaItem,
6
6
  SchemaForUI,
7
7
  PropPanelWidgetProps,
8
8
  PropPanelSchema,
9
+ Schema,
9
10
  } from '@pdfme/common';
10
11
  import type { SidebarProps } from '../../../../types.js';
11
12
  import { Menu } from 'lucide-react';
@@ -45,6 +46,12 @@ const DetailView = (props: DetailViewProps) => {
45
46
  const pluginsRegistry = useContext(PluginsRegistry);
46
47
  const options = useContext(OptionsContext);
47
48
 
49
+ // Define a type-safe i18n function that accepts string keys
50
+ const typedI18n = (key: string): string => {
51
+ // Use a type assertion to handle the union type constraint
52
+ return typeof i18n === 'function' ? i18n(key as keyof Dict) : key;
53
+ };
54
+
48
55
  const [widgets, setWidgets] = useState<{
49
56
  [key: string]: (props: PropPanelWidgetProps) => React.JSX.Element;
50
57
  }>({});
@@ -66,7 +73,7 @@ const DetailView = (props: DetailViewProps) => {
66
73
  {...props}
67
74
  options={options}
68
75
  theme={token}
69
- i18n={i18n as (key: keyof Dict | string) => string}
76
+ i18n={typedI18n}
70
77
  widget={widgetValue}
71
78
  />
72
79
  );
@@ -76,8 +83,11 @@ const DetailView = (props: DetailViewProps) => {
76
83
  }, [activeSchema, pluginsRegistry, JSON.stringify(options)]);
77
84
 
78
85
  useEffect(() => {
79
- const values: any = { ...activeSchema };
80
- values.editable = !values.readOnly;
86
+ // Create a type-safe copy of the schema with editable property
87
+ const values: Record<string, unknown> = { ...activeSchema };
88
+ // Safely access and set properties
89
+ const readOnly = typeof values.readOnly === 'boolean' ? values.readOnly : false;
90
+ values.editable = !readOnly;
81
91
  form.setValues(values);
82
92
  }, [activeSchema, form]);
83
93
 
@@ -96,14 +106,21 @@ const DetailView = (props: DetailViewProps) => {
96
106
  };
97
107
  }, [schemasList, activeSchema]);
98
108
 
99
- const uniqueSchemaName = useRef((value: string): boolean => true);
109
+ // Reference to a function that validates schema name uniqueness
110
+ const uniqueSchemaName = useRef(
111
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
112
+ (_unused: string): boolean => true,
113
+ );
100
114
 
101
- const validateUniqueSchemaName = (_: any, value: string): boolean =>
115
+ // Use proper type for validator function parameter
116
+ const validateUniqueSchemaName = (_: unknown, value: string): boolean =>
102
117
  uniqueSchemaName.current(value);
103
118
 
104
- const handleWatch = debounce((formSchema: any) => {
105
- const formAndSchemaValuesDiffer = (formValue: any, schemaValue: any): boolean => {
106
- if (typeof formValue === 'object') {
119
+ // Use explicit type for debounce function that matches the expected signature
120
+ const handleWatch = debounce(function (...args: unknown[]) {
121
+ const formSchema = args[0] as Record<string, unknown>;
122
+ const formAndSchemaValuesDiffer = (formValue: unknown, schemaValue: unknown): boolean => {
123
+ if (typeof formValue === 'object' && formValue !== null) {
107
124
  return JSON.stringify(formValue) !== JSON.stringify(schemaValue);
108
125
  }
109
126
  return formValue !== schemaValue;
@@ -114,7 +131,7 @@ const DetailView = (props: DetailViewProps) => {
114
131
  if (['id', 'content'].includes(key)) continue;
115
132
 
116
133
  let value = formSchema[key];
117
- if (formAndSchemaValuesDiffer(value, (activeSchema as any)[key])) {
134
+ if (formAndSchemaValuesDiffer(value, (activeSchema as Record<string, unknown>)[key])) {
118
135
  // FIXME memo: https://github.com/pdfme/pdfme/pull/367#issuecomment-1857468274
119
136
  if (value === null && ['rotate', 'opacity'].includes(key)) {
120
137
  value = undefined;
@@ -154,28 +171,100 @@ const DetailView = (props: DetailViewProps) => {
154
171
  }
155
172
  }, 100);
156
173
 
157
- const activePlugin = Object.values(pluginsRegistry).find(
158
- (plugin) => plugin?.propPanel.defaultSchema.type === activeSchema.type,
159
- )!;
174
+ // Find the active plugin with proper type safety
175
+ const activePlugin = Object.values(pluginsRegistry).find((plugin) => {
176
+ if (!plugin || typeof plugin !== 'object') return false;
177
+ if (!plugin.propPanel || typeof plugin.propPanel !== 'object') return false;
178
+ if (!plugin.propPanel.defaultSchema || typeof plugin.propPanel.defaultSchema !== 'object')
179
+ return false;
180
+
181
+ const defaultSchema = plugin.propPanel.defaultSchema as Record<string, unknown>;
182
+ return (
183
+ 'type' in defaultSchema &&
184
+ typeof defaultSchema.type === 'string' &&
185
+ defaultSchema.type === activeSchema.type
186
+ );
187
+ });
160
188
 
161
- const activePropPanelSchema = activePlugin?.propPanel.schema;
189
+ // Safely access the propPanel schema
190
+ const activePropPanelSchema = activePlugin?.propPanel?.schema;
162
191
  if (!activePropPanelSchema) {
163
192
  console.error(`[@pdfme/ui] No propPanel.schema for ${activeSchema.type}.
164
193
  Check this document: https://pdfme.com/docs/custom-schemas`);
165
194
  }
166
195
 
167
- const typeOptions = Object.entries(pluginsRegistry).map(([label, value]) => ({
168
- label,
169
- value: value?.propPanel.defaultSchema.type,
170
- }));
171
- const defaultSchema = activePlugin.propPanel.defaultSchema;
196
+ // Create type-safe options for the type dropdown
197
+ // Create a type-safe options array for the dropdown
198
+ const typeOptions: Array<{ label: string; value: string | undefined }> = [];
199
+
200
+ // Safely populate the options array
201
+ Object.entries(pluginsRegistry).forEach(([label, value]) => {
202
+ // Skip invalid plugins
203
+ if (!value || typeof value !== 'object') {
204
+ typeOptions.push({ label, value: undefined });
205
+ return;
206
+ }
207
+
208
+ if (!('propPanel' in value) || !value.propPanel || typeof value.propPanel !== 'object') {
209
+ typeOptions.push({ label, value: undefined });
210
+ return;
211
+ }
212
+
213
+ if (
214
+ !('defaultSchema' in value.propPanel) ||
215
+ !value.propPanel.defaultSchema ||
216
+ typeof value.propPanel.defaultSchema !== 'object'
217
+ ) {
218
+ typeOptions.push({ label, value: undefined });
219
+ return;
220
+ }
221
+
222
+ // Safely extract the type
223
+ const defaultSchema = value.propPanel.defaultSchema as Record<string, unknown>;
224
+ let schemaType: string | undefined = undefined;
225
+
226
+ if ('type' in defaultSchema && typeof defaultSchema.type === 'string') {
227
+ schemaType = defaultSchema.type;
228
+ }
229
+
230
+ typeOptions.push({ label, value: schemaType });
231
+ });
232
+ // Create a safe empty schema as fallback
233
+ const emptySchema: Record<string, unknown> = {};
172
234
 
235
+ // Safely access the default schema with proper null checking
236
+ const defaultSchema: Record<string, unknown> = activePlugin?.propPanel?.defaultSchema
237
+ ? // Create a safe copy of the schema
238
+ (() => {
239
+ // First check if the defaultSchema is an object
240
+ if (
241
+ typeof activePlugin.propPanel.defaultSchema !== 'object' ||
242
+ activePlugin.propPanel.defaultSchema === null
243
+ ) {
244
+ return emptySchema;
245
+ }
246
+
247
+ // Create a safe copy
248
+ const result: Record<string, unknown> = {};
249
+
250
+ // Only copy properties that exist on the object
251
+ for (const key in activePlugin.propPanel.defaultSchema) {
252
+ if (Object.prototype.hasOwnProperty.call(activePlugin.propPanel.defaultSchema, key)) {
253
+ result[key] = (activePlugin.propPanel.defaultSchema as Record<string, unknown>)[key];
254
+ }
255
+ }
256
+
257
+ return result;
258
+ })()
259
+ : emptySchema;
260
+
261
+ // Create a type-safe schema object
173
262
  const propPanelSchema: PropPanelSchema = {
174
263
  type: 'object',
175
264
  column: 2,
176
265
  properties: {
177
266
  type: {
178
- title: i18n('type'),
267
+ title: typedI18n('type'),
179
268
  type: 'string',
180
269
  widget: 'select',
181
270
  props: { options: typeOptions },
@@ -183,32 +272,32 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
183
272
  span: 12,
184
273
  },
185
274
  name: {
186
- title: i18n('fieldName'),
275
+ title: typedI18n('fieldName'),
187
276
  type: 'string',
188
277
  required: true,
189
278
  span: 12,
190
279
  rules: [
191
280
  {
192
281
  validator: validateUniqueSchemaName,
193
- message: i18n('validation.uniqueName'),
282
+ message: typedI18n('validation.uniqueName'),
194
283
  },
195
284
  ],
196
285
  props: { autoComplete: 'off' },
197
286
  },
198
287
  editable: {
199
- title: i18n('editable'),
288
+ title: typedI18n('editable'),
200
289
  type: 'boolean',
201
290
  span: 8,
202
- hidden: defaultSchema?.readOnly !== undefined,
291
+ hidden: typeof defaultSchema.readOnly !== 'undefined',
203
292
  },
204
293
  required: {
205
- title: i18n('required'),
294
+ title: typedI18n('required'),
206
295
  type: 'boolean',
207
296
  span: 16,
208
297
  hidden: '{{!formData.editable}}',
209
298
  },
210
299
  '-': { type: 'void', widget: 'Divider' },
211
- align: { title: i18n('align'), type: 'void', widget: 'AlignWidget' },
300
+ align: { title: typedI18n('align'), type: 'void', widget: 'AlignWidget' },
212
301
  position: {
213
302
  type: 'object',
214
303
  widget: 'card',
@@ -218,7 +307,7 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
218
307
  },
219
308
  },
220
309
  width: {
221
- title: i18n('width'),
310
+ title: typedI18n('width'),
222
311
  type: 'number',
223
312
  widget: 'inputNumber',
224
313
  required: true,
@@ -226,7 +315,7 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
226
315
  props: { min: 0 },
227
316
  },
228
317
  height: {
229
- title: i18n('height'),
318
+ title: typedI18n('height'),
230
319
  type: 'number',
231
320
  widget: 'inputNumber',
232
321
  required: true,
@@ -234,46 +323,79 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
234
323
  props: { min: 0 },
235
324
  },
236
325
  rotate: {
237
- title: i18n('rotate'),
326
+ title: typedI18n('rotate'),
238
327
  type: 'number',
239
328
  widget: 'inputNumber',
240
- disabled: defaultSchema?.rotate === undefined,
329
+ disabled: typeof defaultSchema.rotate === 'undefined',
241
330
  max: 360,
242
331
  props: { min: 0 },
243
332
  span: 6,
244
333
  },
245
334
  opacity: {
246
- title: i18n('opacity'),
335
+ title: typedI18n('opacity'),
247
336
  type: 'number',
248
337
  widget: 'inputNumber',
249
- disabled: defaultSchema?.opacity === undefined,
338
+ disabled: typeof defaultSchema.opacity === 'undefined',
250
339
  props: { step: 0.1, min: 0, max: 1 },
251
340
  span: 6,
252
341
  },
253
342
  },
254
343
  };
255
344
 
345
+ // Create a safe copy of the properties
346
+ const safeProperties = { ...propPanelSchema.properties };
347
+
256
348
  if (typeof activePropPanelSchema === 'function') {
257
- const { schemasList: _, ...propPanelProps } = props;
349
+ // Create a new object without the schemasList property
350
+ const { size, schemas, pageSize, changeSchemas, activeElements, deselectSchema, activeSchema } =
351
+ props;
352
+ const propPanelProps = {
353
+ size,
354
+ schemas,
355
+ pageSize,
356
+ changeSchemas,
357
+ activeElements,
358
+ deselectSchema,
359
+ activeSchema,
360
+ };
258
361
 
259
- const apps =
260
- activePropPanelSchema({
261
- ...propPanelProps,
262
- options,
263
- theme: token,
264
- i18n: i18n as (key: keyof Dict | string) => string,
265
- }) || {};
362
+ // Use the typedI18n function to avoid type issues
363
+ const functionResult = activePropPanelSchema({
364
+ ...propPanelProps,
365
+ options,
366
+ theme: token,
367
+ i18n: typedI18n,
368
+ });
369
+
370
+ // Safely handle the result
371
+ const apps = functionResult && typeof functionResult === 'object' ? functionResult : {};
372
+
373
+ // Create a divider if needed
374
+ const dividerObj =
375
+ Object.keys(apps).length === 0 ? {} : { '--': { type: 'void', widget: 'Divider' } };
376
+
377
+ // Assign properties safely - use type assertion to satisfy TypeScript
266
378
  propPanelSchema.properties = {
267
- ...propPanelSchema.properties,
268
- ...(Object.keys(apps).length === 0 ? {} : { '--': { type: 'void', widget: 'Divider' } }),
269
- ...apps,
379
+ ...safeProperties,
380
+ ...(dividerObj as Record<string, Partial<Schema>>),
381
+ ...(apps as Record<string, Partial<Schema>>),
270
382
  };
271
383
  } else {
272
- const apps = activePropPanelSchema || {};
384
+ // Handle non-function case
385
+ const apps =
386
+ activePropPanelSchema && typeof activePropPanelSchema === 'object'
387
+ ? activePropPanelSchema
388
+ : {};
389
+
390
+ // Create a divider if needed
391
+ const dividerObj =
392
+ Object.keys(apps).length === 0 ? {} : { '--': { type: 'void', widget: 'Divider' } };
393
+
394
+ // Assign properties safely - use type assertion to satisfy TypeScript
273
395
  propPanelSchema.properties = {
274
- ...propPanelSchema.properties,
275
- ...(Object.keys(apps).length === 0 ? {} : { '--': { type: 'void', widget: 'Divider' } }),
276
- ...apps,
396
+ ...safeProperties,
397
+ ...(dividerObj as Record<string, Partial<Schema>>),
398
+ ...(apps as Record<string, Partial<Schema>>),
277
399
  };
278
400
  }
279
401
 
@@ -292,7 +414,7 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
292
414
  icon={<Menu strokeWidth={1.5} size={20} />}
293
415
  />
294
416
  <Text strong style={{ textAlign: 'center', width: '100%' }}>
295
- {i18n('editField')}
417
+ {typedI18n('editField')}
296
418
  </Text>
297
419
  </div>
298
420
  <Divider style={{ marginTop: token.marginXS, marginBottom: token.marginXS }} />
@@ -6,127 +6,153 @@ import { Button, Typography } from 'antd';
6
6
 
7
7
  const { Text } = Typography;
8
8
 
9
+ // Define prop types for Item component
9
10
  interface Props {
11
+ /** Content to display in the item */
10
12
  value: React.ReactNode;
13
+ /** Optional icon to display */
11
14
  icon?: React.ReactNode;
15
+ /** Custom styles for the item */
12
16
  style?: React.CSSProperties;
17
+ /** Status indicator for the item */
13
18
  status?: 'is-warning' | 'is-danger';
19
+ /** Title attribute for the item */
14
20
  title?: string;
21
+ /** Whether the item is required */
15
22
  required?: boolean;
23
+ /** Whether the item is read-only */
16
24
  readOnly?: boolean;
25
+ /** Whether the item is being dragged as an overlay */
17
26
  dragOverlay?: boolean;
27
+ /** Click handler for the item */
18
28
  onClick?: () => void;
29
+ /** Mouse enter handler */
19
30
  onMouseEnter?: () => void;
31
+ /** Mouse leave handler */
20
32
  onMouseLeave?: () => void;
33
+ /** Whether the item is currently being dragged */
21
34
  dragging?: boolean;
35
+ /** Whether items are being sorted */
22
36
  sorting?: boolean;
37
+ /** CSS transition value */
23
38
  transition?: string;
39
+ /** Transform data for the item */
24
40
  transform?: { x: number; y: number; scaleX: number; scaleY: number } | null;
41
+ /** Whether to fade the item in */
25
42
  fadeIn?: boolean;
43
+ /** Drag listeners from dnd-kit */
26
44
  listeners?: DraggableSyntheticListeners;
27
45
  }
46
+ // Using React.memo and forwardRef for optimized rendering
47
+ // Using TypeScript interface for prop validation instead of PropTypes
28
48
  const Item = React.memo(
29
- React.forwardRef<HTMLLIElement, Props>(
30
- (
31
- {
32
- icon,
33
- value,
34
- status,
35
- title,
36
- required,
37
- readOnly,
38
- style,
39
- dragOverlay,
40
- onClick,
41
- onMouseEnter,
42
- onMouseLeave,
43
- dragging,
44
- fadeIn,
45
- listeners,
46
- sorting,
47
- transition,
48
- transform,
49
- ...props
50
- },
51
- ref,
52
- ) => {
53
- const i18n = useContext(I18nContext);
49
+ /* eslint-disable react/prop-types */
50
+ React.forwardRef<HTMLLIElement, Props>(function Item(
51
+ {
52
+ icon,
53
+ value,
54
+ status,
55
+ title,
56
+ required,
57
+ readOnly,
58
+ style,
59
+ dragOverlay,
60
+ onClick,
61
+ onMouseEnter,
62
+ onMouseLeave,
63
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
64
+ dragging,
65
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
66
+ fadeIn,
67
+ listeners,
68
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
69
+ sorting,
70
+ transition,
71
+ transform,
72
+ ...props
73
+ },
74
+ ref,
75
+ ) {
76
+ /* eslint-enable react/prop-types */
77
+ const i18n = useContext(I18nContext);
54
78
 
55
- useEffect(() => {
56
- if (!dragOverlay) {
57
- return;
58
- }
79
+ useEffect(() => {
80
+ if (!dragOverlay) {
81
+ return;
82
+ }
59
83
 
60
- document.body.style.cursor = 'grabbing';
84
+ document.body.style.cursor = 'grabbing';
61
85
 
62
- return () => {
63
- document.body.style.cursor = '';
64
- };
65
- }, [dragOverlay]);
86
+ return () => {
87
+ document.body.style.cursor = '';
88
+ };
89
+ }, [dragOverlay]);
66
90
 
67
- const { x, y, scaleX, scaleY } = transform || { x: 0, y: 0, scaleX: 1, scaleY: 1 };
91
+ const { x, y, scaleX, scaleY } = transform || { x: 0, y: 0, scaleX: 1, scaleY: 1 };
68
92
 
69
- return (
70
- <li
93
+ return (
94
+ <li
95
+ style={{
96
+ marginTop: 10,
97
+ transition,
98
+ transform: `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`,
99
+ }}
100
+ onMouseEnter={onMouseEnter}
101
+ onMouseLeave={onMouseLeave}
102
+ ref={ref}
103
+ >
104
+ <div
71
105
  style={{
72
- marginTop: 10,
73
- transition,
74
- transform: `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`,
106
+ display: 'flex',
107
+ alignItems: 'center',
108
+ cursor: 'pointer',
109
+ gap: '0.5rem',
110
+ ...style,
75
111
  }}
76
- onMouseEnter={onMouseEnter}
77
- onMouseLeave={onMouseLeave}
78
- ref={ref}
112
+ {...props}
113
+ onClick={() => onClick && onClick()}
79
114
  >
80
- <div
115
+ <Button
116
+ {...listeners}
81
117
  style={{
82
118
  display: 'flex',
83
119
  alignItems: 'center',
84
- cursor: 'pointer',
85
- gap: '0.5rem',
86
- ...style,
120
+ background: 'none',
121
+ boxShadow: 'none',
122
+ border: 'none',
123
+ paddingLeft: '0.25rem',
124
+ }}
125
+ icon={<GripVertical size={15} style={{ cursor: 'grab' }} />}
126
+ />
127
+ {icon}
128
+ <Text
129
+ style={{
130
+ overflow: 'hidden',
131
+ whiteSpace: 'nowrap',
132
+ textOverflow: 'ellipsis',
133
+ width: '100%',
87
134
  }}
88
- {...props}
89
- onClick={() => onClick && onClick()}
135
+ title={title || ''}
90
136
  >
91
- <Button
92
- {...listeners}
93
- style={{
94
- display: 'flex',
95
- alignItems: 'center',
96
- background: 'none',
97
- boxShadow: 'none',
98
- border: 'none',
99
- paddingLeft: '0.25rem',
100
- }}
101
- icon={<GripVertical size={15} style={{ cursor: 'grab' }} />}
102
- />
103
- {icon}
104
- <Text
105
- style={{
106
- overflow: 'hidden',
107
- whiteSpace: 'nowrap',
108
- textOverflow: 'ellipsis',
109
- width: '100%',
110
- }}
111
- title={title || ''}
112
- >
113
- {status === undefined ? (
114
- value
115
- ) : (
116
- <span style={{ display: 'flex', alignItems: 'center' }}>
117
- <CircleAlert size={15} style={{ marginRight: '0.25rem' }} />
118
- {status === 'is-warning' ? i18n('noKeyName') : value}
119
- {status === 'is-danger' ? i18n('notUniq') : ''}
120
- </span>
121
- )}
122
- </Text>
123
- {readOnly && <Lock size={15} style={{ marginRight: '0.5rem' }} />}
124
- {required && <span style={{ color: 'red', marginRight: '0.5rem' }}>*</span>}
125
- </div>
126
- </li>
127
- );
128
- },
129
- ),
137
+ {status === undefined ? (
138
+ value
139
+ ) : (
140
+ <span style={{ display: 'flex', alignItems: 'center' }}>
141
+ <CircleAlert size={15} style={{ marginRight: '0.25rem' }} />
142
+ {status === 'is-warning' ? i18n('noKeyName') : value}
143
+ {status === 'is-danger' ? i18n('notUniq') : ''}
144
+ </span>
145
+ )}
146
+ </Text>
147
+ {readOnly && <Lock size={15} style={{ marginRight: '0.5rem' }} />}
148
+ {required && <span style={{ color: 'red', marginRight: '0.5rem' }}>*</span>}
149
+ </div>
150
+ </li>
151
+ );
152
+ }),
130
153
  );
131
154
 
155
+ // Set display name for debugging
156
+ Item.displayName = 'Item';
157
+
132
158
  export default Item;
@@ -59,12 +59,32 @@ const SelectableSortableContainer = (
59
59
  };
60
60
 
61
61
  const getPluginIcon = (inSchema: string | SchemaForUI): ReactNode => {
62
+ // Get schema by ID or use directly
62
63
  const thisSchema =
63
64
  typeof inSchema === 'string' ? schemas.find((schema) => schema.id === inSchema) : inSchema;
64
65
 
65
- const [pluginLabel, activePlugin] = Object.entries(pluginsRegistry).find(
66
- ([label, plugin]) => plugin?.propPanel.defaultSchema.type === thisSchema?.type,
67
- )!;
66
+ if (!thisSchema) return <></>;
67
+
68
+ // Safely extract schema type
69
+ const schemaType = typeof thisSchema.type === 'string' ? thisSchema.type : '';
70
+
71
+ // Find matching plugin with type-safe approach
72
+ const pluginEntry = Object.entries(pluginsRegistry).find(([, plugin]) => {
73
+ if (!plugin || typeof plugin !== 'object') return false;
74
+ if (!plugin.propPanel || typeof plugin.propPanel !== 'object') return false;
75
+ if (!plugin.propPanel.defaultSchema || typeof plugin.propPanel.defaultSchema !== 'object')
76
+ return false;
77
+
78
+ // Use Record<string, unknown> to safely access properties
79
+ const defaultSchema = plugin.propPanel.defaultSchema as Record<string, unknown>;
80
+ return (
81
+ 'type' in defaultSchema &&
82
+ typeof defaultSchema.type === 'string' &&
83
+ defaultSchema.type === schemaType
84
+ );
85
+ });
86
+
87
+ const [pluginLabel, activePlugin] = pluginEntry || ['', undefined];
68
88
 
69
89
  if (!activePlugin) {
70
90
  return <></>;
@@ -39,12 +39,29 @@ const SelectableSortableItem = ({
39
39
 
40
40
  const newListeners = {
41
41
  ...listeners,
42
- onClick: (event: any) => onSelect(schema.id, event.shiftKey),
42
+ onClick: (event: React.MouseEvent) => onSelect(schema.id, event.shiftKey),
43
43
  };
44
44
 
45
- const [pluginLabel, thisPlugin] = Object.entries(pluginsRegistry).find(
46
- ([label, plugin]) => plugin?.propPanel.defaultSchema.type === schema.type,
47
- )!;
45
+ // Safely extract schema type
46
+ const schemaType = typeof schema.type === 'string' ? schema.type : '';
47
+
48
+ // Find matching plugin with type-safe approach
49
+ const pluginEntry = Object.entries(pluginsRegistry).find(([, plugin]) => {
50
+ if (!plugin || typeof plugin !== 'object') return false;
51
+ if (!plugin.propPanel || typeof plugin.propPanel !== 'object') return false;
52
+ if (!plugin.propPanel.defaultSchema || typeof plugin.propPanel.defaultSchema !== 'object')
53
+ return false;
54
+
55
+ // Use Record<string, unknown> to safely access properties
56
+ const defaultSchema = plugin.propPanel.defaultSchema as Record<string, unknown>;
57
+ return (
58
+ 'type' in defaultSchema &&
59
+ typeof defaultSchema.type === 'string' &&
60
+ defaultSchema.type === schemaType
61
+ );
62
+ });
63
+
64
+ const [pluginLabel, thisPlugin] = pluginEntry || ['', undefined];
48
65
 
49
66
  let status: undefined | 'is-warning' | 'is-danger';
50
67
  if (!schema.name) {