@memberjunction/react-runtime 2.75.0 → 2.77.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 (52) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +23 -0
  3. package/dist/compiler/component-compiler.d.ts +0 -1
  4. package/dist/compiler/component-compiler.d.ts.map +1 -1
  5. package/dist/compiler/component-compiler.js +34 -25
  6. package/dist/index.d.ts +4 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +10 -2
  9. package/dist/registry/component-registry.d.ts +1 -0
  10. package/dist/registry/component-registry.d.ts.map +1 -1
  11. package/dist/registry/component-registry.js +6 -3
  12. package/dist/runtime/index.d.ts +2 -1
  13. package/dist/runtime/index.d.ts.map +1 -1
  14. package/dist/runtime/index.js +4 -2
  15. package/dist/runtime/prop-builder.d.ts +1 -2
  16. package/dist/runtime/prop-builder.d.ts.map +1 -1
  17. package/dist/runtime/prop-builder.js +4 -76
  18. package/dist/runtime/react-root-manager.d.ts +26 -0
  19. package/dist/runtime/react-root-manager.d.ts.map +1 -0
  20. package/dist/runtime/react-root-manager.js +122 -0
  21. package/dist/types/index.d.ts +1 -0
  22. package/dist/types/index.d.ts.map +1 -1
  23. package/dist/utilities/cache-manager.d.ts +38 -0
  24. package/dist/utilities/cache-manager.d.ts.map +1 -0
  25. package/dist/utilities/cache-manager.js +156 -0
  26. package/dist/utilities/core-libraries.d.ts +5 -0
  27. package/dist/utilities/core-libraries.d.ts.map +1 -0
  28. package/dist/utilities/core-libraries.js +52 -0
  29. package/dist/utilities/index.d.ts +2 -0
  30. package/dist/utilities/index.d.ts.map +1 -1
  31. package/dist/utilities/index.js +2 -0
  32. package/dist/utilities/library-loader.d.ts +2 -2
  33. package/dist/utilities/library-loader.d.ts.map +1 -1
  34. package/dist/utilities/library-loader.js +52 -24
  35. package/dist/utilities/resource-manager.d.ts +34 -0
  36. package/dist/utilities/resource-manager.d.ts.map +1 -0
  37. package/dist/utilities/resource-manager.js +174 -0
  38. package/package.json +4 -4
  39. package/samples/entities-1.js +493 -0
  40. package/src/compiler/component-compiler.ts +64 -35
  41. package/src/index.ts +18 -1
  42. package/src/registry/component-registry.ts +14 -4
  43. package/src/runtime/index.ts +7 -2
  44. package/src/runtime/prop-builder.ts +5 -112
  45. package/src/runtime/react-root-manager.ts +218 -0
  46. package/src/types/index.ts +2 -0
  47. package/src/utilities/cache-manager.ts +253 -0
  48. package/src/utilities/core-libraries.ts +61 -0
  49. package/src/utilities/index.ts +3 -1
  50. package/src/utilities/library-loader.ts +111 -47
  51. package/src/utilities/resource-manager.ts +305 -0
  52. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resourceManager = exports.ResourceManager = void 0;
4
+ class ResourceManager {
5
+ constructor() {
6
+ this.resources = new Map();
7
+ this.globalResources = new Set();
8
+ this.cleanupCallbacks = new Map();
9
+ }
10
+ setTimeout(componentId, callback, delay, metadata) {
11
+ const id = window.setTimeout(() => {
12
+ this.removeResource(componentId, 'timer', id);
13
+ callback();
14
+ }, delay);
15
+ this.addResource(componentId, {
16
+ type: 'timer',
17
+ id,
18
+ cleanup: () => window.clearTimeout(id),
19
+ metadata
20
+ });
21
+ return id;
22
+ }
23
+ setInterval(componentId, callback, delay, metadata) {
24
+ const id = window.setInterval(callback, delay);
25
+ this.addResource(componentId, {
26
+ type: 'interval',
27
+ id,
28
+ cleanup: () => window.clearInterval(id),
29
+ metadata
30
+ });
31
+ return id;
32
+ }
33
+ requestAnimationFrame(componentId, callback, metadata) {
34
+ const id = window.requestAnimationFrame((time) => {
35
+ this.removeResource(componentId, 'animationFrame', id);
36
+ callback(time);
37
+ });
38
+ this.addResource(componentId, {
39
+ type: 'animationFrame',
40
+ id,
41
+ cleanup: () => window.cancelAnimationFrame(id),
42
+ metadata
43
+ });
44
+ return id;
45
+ }
46
+ clearTimeout(componentId, id) {
47
+ window.clearTimeout(id);
48
+ this.removeResource(componentId, 'timer', id);
49
+ }
50
+ clearInterval(componentId, id) {
51
+ window.clearInterval(id);
52
+ this.removeResource(componentId, 'interval', id);
53
+ }
54
+ cancelAnimationFrame(componentId, id) {
55
+ window.cancelAnimationFrame(id);
56
+ this.removeResource(componentId, 'animationFrame', id);
57
+ }
58
+ addEventListener(componentId, target, type, listener, options) {
59
+ target.addEventListener(type, listener, options);
60
+ const resourceId = `${type}-${Date.now()}-${Math.random()}`;
61
+ this.addResource(componentId, {
62
+ type: 'eventListener',
63
+ id: resourceId,
64
+ cleanup: () => target.removeEventListener(type, listener, options),
65
+ metadata: { target, type, options }
66
+ });
67
+ }
68
+ registerDOMElement(componentId, element, cleanup) {
69
+ const resourceId = `dom-${Date.now()}-${Math.random()}`;
70
+ this.addResource(componentId, {
71
+ type: 'domElement',
72
+ id: resourceId,
73
+ cleanup: () => {
74
+ if (cleanup) {
75
+ cleanup();
76
+ }
77
+ if (element.parentNode) {
78
+ element.parentNode.removeChild(element);
79
+ }
80
+ },
81
+ metadata: { element }
82
+ });
83
+ }
84
+ registerReactRoot(componentId, root, unmountFn) {
85
+ this.addResource(componentId, {
86
+ type: 'reactRoot',
87
+ id: `react-root-${componentId}`,
88
+ cleanup: unmountFn,
89
+ metadata: { root }
90
+ });
91
+ }
92
+ registerCleanup(componentId, cleanup) {
93
+ if (!this.cleanupCallbacks.has(componentId)) {
94
+ this.cleanupCallbacks.set(componentId, []);
95
+ }
96
+ this.cleanupCallbacks.get(componentId).push(cleanup);
97
+ }
98
+ registerGlobalResource(resource) {
99
+ this.globalResources.add(resource);
100
+ }
101
+ addResource(componentId, resource) {
102
+ if (!this.resources.has(componentId)) {
103
+ this.resources.set(componentId, new Set());
104
+ }
105
+ this.resources.get(componentId).add(resource);
106
+ }
107
+ removeResource(componentId, type, id) {
108
+ const componentResources = this.resources.get(componentId);
109
+ if (componentResources) {
110
+ const toRemove = Array.from(componentResources).find(r => r.type === type && r.id === id);
111
+ if (toRemove) {
112
+ componentResources.delete(toRemove);
113
+ }
114
+ }
115
+ }
116
+ cleanupComponent(componentId) {
117
+ const componentResources = this.resources.get(componentId);
118
+ if (componentResources) {
119
+ componentResources.forEach(resource => {
120
+ try {
121
+ resource.cleanup();
122
+ }
123
+ catch (error) {
124
+ console.error(`Error cleaning up ${resource.type} resource:`, error);
125
+ }
126
+ });
127
+ this.resources.delete(componentId);
128
+ }
129
+ const callbacks = this.cleanupCallbacks.get(componentId);
130
+ if (callbacks) {
131
+ callbacks.forEach(callback => {
132
+ try {
133
+ callback();
134
+ }
135
+ catch (error) {
136
+ console.error('Error executing cleanup callback:', error);
137
+ }
138
+ });
139
+ this.cleanupCallbacks.delete(componentId);
140
+ }
141
+ }
142
+ cleanupGlobal() {
143
+ this.globalResources.forEach(resource => {
144
+ try {
145
+ resource.cleanup();
146
+ }
147
+ catch (error) {
148
+ console.error(`Error cleaning up global ${resource.type} resource:`, error);
149
+ }
150
+ });
151
+ this.globalResources.clear();
152
+ }
153
+ cleanupAll() {
154
+ for (const componentId of this.resources.keys()) {
155
+ this.cleanupComponent(componentId);
156
+ }
157
+ this.cleanupGlobal();
158
+ }
159
+ getStats() {
160
+ const resourceCounts = {};
161
+ for (const resources of this.resources.values()) {
162
+ resources.forEach(resource => {
163
+ resourceCounts[resource.type] = (resourceCounts[resource.type] || 0) + 1;
164
+ });
165
+ }
166
+ return {
167
+ componentCount: this.resources.size,
168
+ resourceCounts,
169
+ globalResourceCount: this.globalResources.size
170
+ };
171
+ }
172
+ }
173
+ exports.ResourceManager = ResourceManager;
174
+ exports.resourceManager = new ResourceManager();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/react-runtime",
3
- "version": "2.75.0",
3
+ "version": "2.77.0",
4
4
  "description": "Platform-agnostic React component runtime for MemberJunction. Provides core compilation, registry, and execution capabilities for React components in any JavaScript environment.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,9 +25,9 @@
25
25
  },
26
26
  "homepage": "https://github.com/MemberJunction/MJ#readme",
27
27
  "dependencies": {
28
- "@memberjunction/core": "2.75.0",
29
- "@memberjunction/global": "2.75.0",
30
- "@memberjunction/interactive-component-types": "2.75.0",
28
+ "@memberjunction/core": "2.77.0",
29
+ "@memberjunction/global": "2.77.0",
30
+ "@memberjunction/interactive-component-types": "2.77.0",
31
31
  "@babel/standalone": "^7.23.5",
32
32
  "rxjs": "^7.8.1"
33
33
  },
@@ -0,0 +1,493 @@
1
+ function EntityFieldSummaryTable({ data, utilities, styles, components, onStateChanged, filters, sortBy, sortDirection }) {
2
+ const { TableComponent, FiltersPanel } = components;
3
+
4
+ const handleSort = (column, direction) => {
5
+ onStateChanged?.({ sortBy: column, sortDirection: direction });
6
+ };
7
+
8
+ const handleFiltersChange = (updatedFilters) => {
9
+ onStateChanged?.({ filters: updatedFilters });
10
+ };
11
+
12
+ const columns = [
13
+ {
14
+ key: 'EntityName',
15
+ title: 'Entity Name',
16
+ dataIndex: 'EntityName',
17
+ width: 200,
18
+ ellipsis: true,
19
+ sortable: true,
20
+ render: (text) => (
21
+ <div style={{
22
+ maxWidth: '200px',
23
+ whiteSpace: 'nowrap',
24
+ overflow: 'hidden',
25
+ textOverflow: 'ellipsis',
26
+ cursor: 'pointer',
27
+ color: styles.colors.primary
28
+ }} title={text}>
29
+ {text}
30
+ </div>
31
+ )
32
+ },
33
+ {
34
+ key: 'EntityDescription',
35
+ title: 'Description',
36
+ dataIndex: 'EntityDescription',
37
+ width: 300,
38
+ ellipsis: true,
39
+ sortable: false,
40
+ render: (text) => (
41
+ <div style={{
42
+ maxWidth: '300px',
43
+ whiteSpace: 'nowrap',
44
+ overflow: 'hidden',
45
+ textOverflow: 'ellipsis',
46
+ color: styles.colors.textSecondary
47
+ }} title={text}>
48
+ {text || 'No description'}
49
+ </div>
50
+ )
51
+ },
52
+ {
53
+ key: 'TotalFields',
54
+ title: 'Total Fields',
55
+ dataIndex: 'TotalFields',
56
+ width: 100,
57
+ sortable: true,
58
+ align: 'right',
59
+ render: (value) => (
60
+ <span style={{
61
+ fontWeight: styles.typography.fontWeight?.medium || '500',
62
+ color: styles.colors.text
63
+ }}>
64
+ {value || 0}
65
+ </span>
66
+ )
67
+ },
68
+ {
69
+ key: 'TextFields',
70
+ title: 'Text',
71
+ dataIndex: 'TextFields',
72
+ width: 80,
73
+ sortable: true,
74
+ align: 'right',
75
+ render: (value) => (
76
+ <span style={{ color: styles.colors.info }}>
77
+ {value || 0}
78
+ </span>
79
+ )
80
+ },
81
+ {
82
+ key: 'NumberFields',
83
+ title: 'Number',
84
+ dataIndex: 'NumberFields',
85
+ width: 80,
86
+ sortable: true,
87
+ align: 'right',
88
+ render: (value) => (
89
+ <span style={{ color: styles.colors.success }}>
90
+ {value || 0}
91
+ </span>
92
+ )
93
+ },
94
+ {
95
+ key: 'DateFields',
96
+ title: 'Date',
97
+ dataIndex: 'DateFields',
98
+ width: 80,
99
+ sortable: true,
100
+ align: 'right',
101
+ render: (value) => (
102
+ <span style={{ color: styles.colors.warning }}>
103
+ {value || 0}
104
+ </span>
105
+ )
106
+ },
107
+ {
108
+ key: 'BooleanFields',
109
+ title: 'Boolean',
110
+ dataIndex: 'BooleanFields',
111
+ width: 80,
112
+ sortable: true,
113
+ align: 'right',
114
+ render: (value) => (
115
+ <span style={{ color: styles.colors.error }}>
116
+ {value || 0}
117
+ </span>
118
+ )
119
+ },
120
+ {
121
+ key: 'GuidFields',
122
+ title: 'GUID',
123
+ dataIndex: 'GuidFields',
124
+ width: 80,
125
+ sortable: true,
126
+ align: 'right',
127
+ render: (value) => (
128
+ <span style={{ color: styles.colors.textSecondary }}>
129
+ {value || 0}
130
+ </span>
131
+ )
132
+ },
133
+ {
134
+ key: 'BinaryFields',
135
+ title: 'Binary',
136
+ dataIndex: 'BinaryFields',
137
+ width: 80,
138
+ sortable: true,
139
+ align: 'right',
140
+ render: (value) => (
141
+ <span style={{ color: styles.colors.textTertiary }}>
142
+ {value || 0}
143
+ </span>
144
+ )
145
+ },
146
+ {
147
+ key: 'EntityRowCount',
148
+ title: 'Rows',
149
+ dataIndex: 'EntityRowCount',
150
+ width: 100,
151
+ sortable: true,
152
+ align: 'right',
153
+ render: (value) => (
154
+ <span style={{ fontSize: styles.typography.fontSize.sm }}>
155
+ {value?.toLocaleString() || '0'}
156
+ </span>
157
+ )
158
+ },
159
+ {
160
+ key: 'EntityStatus',
161
+ title: 'Status',
162
+ dataIndex: 'EntityStatus',
163
+ width: 100,
164
+ sortable: true,
165
+ render: (value) => (
166
+ <span style={{
167
+ padding: '4px 8px',
168
+ borderRadius: styles.borders.radius,
169
+ fontSize: styles.typography.fontSize.xs,
170
+ backgroundColor: value === 'Active' ? styles.colors.successLight :
171
+ value === 'Deprecated' ? styles.colors.warningLight :
172
+ styles.colors.errorLight,
173
+ color: value === 'Active' ? styles.colors.success :
174
+ value === 'Deprecated' ? styles.colors.warning :
175
+ styles.colors.error
176
+ }}>
177
+ {value}
178
+ </span>
179
+ )
180
+ },
181
+ {
182
+ key: 'LastFieldUpdate',
183
+ title: 'Last Updated',
184
+ dataIndex: 'LastFieldUpdate',
185
+ width: 150,
186
+ sortable: true,
187
+ render: (value) => (
188
+ <div title={value ? new Date(value).toLocaleString() : ''}>
189
+ <span style={{
190
+ fontSize: styles.typography.fontSize.sm,
191
+ color: styles.colors.textSecondary
192
+ }}>
193
+ {value ? new Date(value).toLocaleDateString() : 'Never'}
194
+ </span>
195
+ </div>
196
+ )
197
+ }
198
+ ];
199
+
200
+ const containerStyle = {
201
+ display: 'flex',
202
+ flexDirection: 'column',
203
+ height: '100%',
204
+ backgroundColor: styles.colors.background,
205
+ fontFamily: styles.typography.fontFamily
206
+ };
207
+
208
+ const filterContainerStyle = {
209
+ padding: styles.spacing.md,
210
+ borderBottom: `1px solid ${styles.colors.border}`,
211
+ backgroundColor: styles.colors.surface
212
+ };
213
+
214
+ const tableContainerStyle = {
215
+ flex: 1,
216
+ overflow: 'auto',
217
+ padding: styles.spacing.md
218
+ };
219
+
220
+ return (
221
+ <div style={containerStyle}>
222
+ <div style={filterContainerStyle}>
223
+ <FiltersPanel
224
+ searchTerm={filters?.searchTerm || ''}
225
+ minFields={filters?.minFields || 0}
226
+ maxFields={filters?.maxFields || 9999}
227
+ selectedStatuses={filters?.selectedStatuses || ['Active']}
228
+ typeFilters={filters?.typeFilters || {}}
229
+ onFiltersChange={handleFiltersChange}
230
+ styles={styles}
231
+ utilities={utilities}
232
+ components={components}
233
+ />
234
+ </div>
235
+ <div style={tableContainerStyle}>
236
+ <TableComponent
237
+ columns={columns}
238
+ data={data || []}
239
+ sortColumn={sortBy}
240
+ sortDirection={sortDirection}
241
+ onSort={handleSort}
242
+ styles={styles}
243
+ utilities={utilities}
244
+ components={components}
245
+ />
246
+ </div>
247
+ </div>
248
+ );
249
+ }
250
+
251
+ /*******************************************************
252
+ TableComponent
253
+ undefined
254
+ *******************************************************/
255
+ function TableComponent({ columns = [], data = [], sortColumn, sortDirection, styles = {}, callbacks = {}, ...userState }) {
256
+ const updateState = (newState) => {
257
+ callbacks?.UpdateUserState?.({ ...userState, ...newState });
258
+ };
259
+
260
+ const handleSort = (columnName) => {
261
+ const newDirection = sortColumn === columnName && sortDirection === 'asc' ? 'desc' : 'asc';
262
+ updateState({ sortColumn: columnName, sortDirection: newDirection });
263
+ };
264
+
265
+ const containerStyle = {
266
+ height: '100%',
267
+ overflow: 'auto',
268
+ backgroundColor: styles.colors?.background || '#ffffff',
269
+ border: `1px solid ${styles.colors?.border || '#e0e0e0'}`,
270
+ borderRadius: styles.borders?.radius || '4px',
271
+ fontFamily: styles.typography?.fontFamily || 'Arial, sans-serif'
272
+ };
273
+
274
+ const tableStyle = {
275
+ width: '100%',
276
+ borderCollapse: 'collapse',
277
+ fontSize: styles.typography?.fontSize?.md || '14px'
278
+ };
279
+
280
+ const headerStyle = {
281
+ backgroundColor: styles.colors?.surface || '#f5f5f5',
282
+ borderBottom: `1px solid ${styles.colors?.border || '#e0e0e0'}`,
283
+ padding: styles.spacing?.md || '12px',
284
+ textAlign: 'left',
285
+ fontWeight: styles.typography?.fontWeight?.semibold || '600',
286
+ color: styles.colors?.text || '#333333',
287
+ cursor: 'pointer',
288
+ userSelect: 'none'
289
+ };
290
+
291
+ const cellStyle = {
292
+ padding: styles.spacing?.md || '12px',
293
+ borderBottom: `1px solid ${styles.colors?.borderLight || styles.colors?.border || '#e0e0e0'}`,
294
+ color: styles.colors?.textSecondary || '#666666'
295
+ };
296
+
297
+ const sortIconStyle = {
298
+ marginLeft: styles.spacing?.xs || '4px',
299
+ fontSize: styles.typography?.fontSize?.sm || '12px'
300
+ };
301
+
302
+ return (
303
+ <div style={containerStyle}>
304
+ <table style={tableStyle}>
305
+ <thead>
306
+ <tr>
307
+ {columns.map(column => (
308
+ <th
309
+ key={column.field}
310
+ style={{
311
+ ...headerStyle,
312
+ ...(sortColumn === column.field
313
+ ? { backgroundColor: styles.colors?.primaryLight || styles.colors?.primary || '#e3f2fd' }
314
+ : {})
315
+ }}
316
+ onClick={() => handleSort(column.field)}
317
+ >
318
+ {column.title}
319
+ {sortColumn === column.field && (
320
+ <span style={sortIconStyle}>
321
+ {sortDirection === 'asc' ? '↑' : '↓'}
322
+ </span>
323
+ )}
324
+ </th>
325
+ ))}
326
+ </tr>
327
+ </thead>
328
+ <tbody>
329
+ {data.map((row, index) => (
330
+ <tr key={index}>
331
+ {columns.map(column => (
332
+ <td
333
+ key={`${index}-${column.field}`}
334
+ style={cellStyle}
335
+ >
336
+ {row[column.field] || '-'}
337
+ </td>
338
+ ))}
339
+ </tr>
340
+ ))}
341
+ </tbody>
342
+ </table>
343
+ </div>
344
+ );
345
+ }
346
+
347
+ /*******************************************************
348
+ FiltersPanel
349
+ undefined
350
+ *******************************************************/
351
+ function FiltersPanel({ data, utilities, styles, components, searchTerm, minFields, maxFields, selectedStatuses, typeFilters, onStateChanged }) {
352
+ const handleSearchChange = (e) => {
353
+ onStateChanged?.({ searchTerm: e.target.value });
354
+ };
355
+
356
+ const handleMinFieldsChange = (e) => {
357
+ const value = parseInt(e.target.value) || 0;
358
+ onStateChanged?.({ minFields: value });
359
+ };
360
+
361
+ const handleMaxFieldsChange = (e) => {
362
+ const value = parseInt(e.target.value) || 0;
363
+ onStateChanged?.({ maxFields: value });
364
+ };
365
+
366
+ const handleStatusToggle = (status) => {
367
+ const updatedStatuses = selectedStatuses.includes(status)
368
+ ? selectedStatuses.filter(s => s !== status)
369
+ : [...selectedStatuses, status];
370
+ onStateChanged?.({ selectedStatuses: updatedStatuses });
371
+ };
372
+
373
+ const handleTypeChange = (typeName, value) => {
374
+ const updatedTypeFilters = { ...typeFilters, [typeName]: value };
375
+ onStateChanged?.({ typeFilters: updatedTypeFilters });
376
+ };
377
+
378
+ const containerStyle = {
379
+ padding: styles.spacing.md,
380
+ backgroundColor: styles.colors.surface,
381
+ border: `1px solid ${styles.colors.border}`,
382
+ borderRadius: styles.borders.radius,
383
+ height: '100%',
384
+ overflow: 'auto'
385
+ };
386
+
387
+ const inputStyle = {
388
+ width: '100%',
389
+ padding: styles.spacing.sm,
390
+ border: `1px solid ${styles.colors.border}`,
391
+ borderRadius: styles.borders.radius,
392
+ fontSize: styles.typography.fontSize.sm,
393
+ marginBottom: styles.spacing.sm
394
+ };
395
+
396
+ const labelStyle = {
397
+ display: 'block',
398
+ marginBottom: styles.spacing.xs,
399
+ fontSize: styles.typography.fontSize.sm,
400
+ color: styles.colors.textSecondary,
401
+ fontWeight: styles.typography.fontWeight?.medium || '500'
402
+ };
403
+
404
+ const statusButtonStyle = (isActive) => ({
405
+ padding: `${styles.spacing.xs} ${styles.spacing.sm}`,
406
+ margin: `0 ${styles.spacing.xs} ${styles.spacing.xs} 0`,
407
+ border: `1px solid ${isActive ? styles.colors.primary : styles.colors.border}`,
408
+ borderRadius: styles.borders.radius,
409
+ backgroundColor: isActive ? styles.colors.primary : 'transparent',
410
+ color: isActive ? styles.colors.textInverse || styles.colors.background : styles.colors.text,
411
+ cursor: 'pointer',
412
+ fontSize: styles.typography.fontSize.xs
413
+ });
414
+
415
+ const numberInputStyle = {
416
+ width: '80px',
417
+ padding: styles.spacing.xs,
418
+ border: `1px solid ${styles.colors.border}`,
419
+ borderRadius: styles.borders.radius,
420
+ fontSize: styles.typography.fontSize.sm,
421
+ margin: `0 ${styles.spacing.sm}`
422
+ };
423
+
424
+ return (
425
+ <div style={containerStyle}>
426
+ <div style={{ marginBottom: styles.spacing.lg }}>
427
+ <label style={labelStyle}>Entity Name Search</label>
428
+ <input
429
+ type="text"
430
+ value={searchTerm || ''}
431
+ onChange={handleSearchChange}
432
+ placeholder="Search entities..."
433
+ style={inputStyle}
434
+ />
435
+ </div>
436
+
437
+ <div style={{ marginBottom: styles.spacing.lg }}>
438
+ <label style={labelStyle}>Field Count Range</label>
439
+ <div style={{ display: 'flex', alignItems: 'center', gap: styles.spacing.sm }}>
440
+ <span>Min:</span>
441
+ <input
442
+ type="number"
443
+ value={minFields || 0}
444
+ onChange={handleMinFieldsChange}
445
+ min="0"
446
+ style={numberInputStyle}
447
+ />
448
+ <span>Max:</span>
449
+ <input
450
+ type="number"
451
+ value={maxFields || 0}
452
+ onChange={handleMaxFieldsChange}
453
+ min="0"
454
+ style={numberInputStyle}
455
+ />
456
+ </div>
457
+ </div>
458
+
459
+ <div style={{ marginBottom: styles.spacing.lg }}>
460
+ <label style={labelStyle}>Status Filters</label>
461
+ <div>
462
+ {['Active', 'Deprecated', 'Disabled'].map(status => (
463
+ <button
464
+ key={status}
465
+ onClick={() => handleStatusToggle(status)}
466
+ style={statusButtonStyle(selectedStatuses.includes(status))}
467
+ >
468
+ {status}
469
+ </button>
470
+ ))}
471
+ </div>
472
+ </div>
473
+
474
+ <div>
475
+ <label style={labelStyle}>Type Distribution Threshold</label>
476
+ {Object.entries(typeFilters || {}).map(([typeName, currentValue]) => (
477
+ <div key={typeName} style={{ marginBottom: styles.spacing.sm }}>
478
+ <label style={{ ...labelStyle, fontSize: styles.typography.fontSize.xs }}>
479
+ {typeName}:
480
+ </label>
481
+ <input
482
+ type="number"
483
+ value={currentValue || 0}
484
+ onChange={(e) => handleTypeChange(typeName, parseInt(e.target.value) || 0)}
485
+ min="0"
486
+ style={numberInputStyle}
487
+ />
488
+ </div>
489
+ ))}
490
+ </div>
491
+ </div>
492
+ );
493
+ }