@integry/sdk 4.7.35 → 4.7.36

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integry/sdk",
3
- "version": "4.7.35",
3
+ "version": "4.7.36",
4
4
  "description": "Integry SDK",
5
5
  "main": "dist/umd/index.umd.js",
6
6
  "module": "dist/esm/index.csm.js",
@@ -2034,6 +2034,7 @@ class ActionForm extends Component<ActionFormPropsType, ActionFormStateType> {
2034
2034
  : null}
2035
2035
  showMenuOnLeft=${this.props
2036
2036
  .showMenuOnLeft || false}
2037
+ showFieldsLimit=${5}
2037
2038
  />
2038
2039
  </div>
2039
2040
  `;
@@ -1,10 +1,11 @@
1
+ 'use client';
2
+
1
3
  import { html } from 'htm/preact';
2
4
  import { connect } from 'unistore/preact';
3
5
  import { useContext, useEffect, useState } from 'preact/hooks';
4
- import { TemplateField, NestedObject } from '@/interfaces';
6
+ import type { TemplateField, NestedObject } from '@/interfaces';
5
7
  import AppContext from '@/contexts/AppContext';
6
- import { StoreType } from '@/types/store';
7
- // import { Input } from '@/components/Input';
8
+ import type { StoreType } from '@/types/store';
8
9
  import { Loader } from '@/components/Loader';
9
10
  import { MultipurposeField } from '@/components/MultipurposeField';
10
11
  import { actionFunctions } from '@/store';
@@ -30,6 +31,7 @@ export type DynamicFieldsProps = {
30
31
  objectValue?: any;
31
32
  tagsTree?: any;
32
33
  showMenuOnLeft?: boolean;
34
+ showFieldsLimit?: 'all' | number; // New prop
33
35
  } & StoreType;
34
36
 
35
37
  interface DynamicDataItem {
@@ -59,7 +61,9 @@ const DynamicFields = (props: DynamicFieldsProps) => {
59
61
  objectValue = null,
60
62
  tagsTree = null,
61
63
  showMenuOnLeft = false,
64
+ showFieldsLimit = 'all', // Default to 'all'
62
65
  } = props;
66
+
63
67
  const [dynamicItems, setDynamicItems] = useState<DynamicDataItem[]>([]);
64
68
  const [loading, setLoading] = useState<boolean>(true);
65
69
  const [customFieldsData, setCustomFieldsData] = useState<any>(
@@ -70,13 +74,19 @@ const DynamicFields = (props: DynamicFieldsProps) => {
70
74
  setIsErrorOnLoadingCustomFields,
71
75
  ] = useState<boolean>(false);
72
76
 
77
+ // New state for field limiting functionality
78
+ const [visibleFieldIds, setVisibleFieldIds] = useState<Set<string>>(
79
+ new Set(),
80
+ );
81
+ const [showFieldSelector, setShowFieldSelector] = useState<boolean>(false);
82
+ const [searchTerm, setSearchTerm] = useState<string>('');
83
+
73
84
  const context = useContext(AppContext);
74
85
  const isReadOnly = context?.isReadOnly;
75
86
 
76
87
  const fetchDynamicFields = async () => {
77
88
  const { activity_field } = dynamicField;
78
- // if configMode is true, then don't fetch dynamic items
79
- // fetch dynamic items from endpoint
89
+
80
90
  if (sourceFlowIntegrataionInvocationUrl && selectedAuthId) {
81
91
  setLoading(true);
82
92
  context?.apiHandler
@@ -138,6 +148,45 @@ const DynamicFields = (props: DynamicFieldsProps) => {
138
148
  }
139
149
  };
140
150
 
151
+ // Initialize visible fields based on showFieldsLimit and existing data
152
+ useEffect(() => {
153
+ if (dynamicItems.length > 0) {
154
+ const parsedVal = objectValue
155
+ ? customFieldsData
156
+ : (props.stepDataMapping[stepId]?.[dynamicField.id]
157
+ ?.objectValue as Record<string, string | number>) || {};
158
+
159
+ // Get fields that have existing values (for edit mode)
160
+ const fieldsWithValues = dynamicItems
161
+ .filter(
162
+ (item) =>
163
+ parsedVal && parsedVal[item.id] && parsedVal[item.id] !== '',
164
+ )
165
+ .map((item) => item.id);
166
+
167
+ if (showFieldsLimit === 'all') {
168
+ // Show all fields
169
+ setVisibleFieldIds(new Set(dynamicItems.map((item) => item.id)));
170
+ } else if (typeof showFieldsLimit === 'number') {
171
+ // Show limited fields + fields with existing values
172
+ const fieldsToShow = new Set(fieldsWithValues);
173
+
174
+ // Add additional fields up to the limit if needed
175
+ let count = fieldsWithValues.length;
176
+ dynamicItems.some((item) => {
177
+ if (count >= showFieldsLimit) return true;
178
+ if (!fieldsToShow.has(item.id)) {
179
+ fieldsToShow.add(item.id);
180
+ count += 1;
181
+ }
182
+ return false;
183
+ });
184
+
185
+ setVisibleFieldIds(fieldsToShow);
186
+ }
187
+ }
188
+ }, [dynamicItems, showFieldsLimit, customFieldsData, objectValue]);
189
+
141
190
  useEffect(() => {
142
191
  fetchDynamicFields();
143
192
  }, [parentFieldChanged]);
@@ -171,17 +220,12 @@ const DynamicFields = (props: DynamicFieldsProps) => {
171
220
  });
172
221
 
173
222
  if (onChangeCallback && objectValue !== null) {
174
- // Use the functional update form of setState to ensure we're working with the latest state
175
223
  setCustomFieldsData((prevData: any) => {
176
224
  const updatedData = {
177
225
  ...prevData,
178
226
  [machineName]: val,
179
227
  };
180
-
181
- // Call the callback with the updated data
182
228
  onChangeCallback(JSON.stringify(updatedData));
183
-
184
- // Return the updated data to set the state
185
229
  return updatedData;
186
230
  });
187
231
  } else {
@@ -194,54 +238,138 @@ const DynamicFields = (props: DynamicFieldsProps) => {
194
238
  }
195
239
  };
196
240
 
241
+ const handleFieldSelect = (fieldId: string) => {
242
+ setVisibleFieldIds((prev) => new Set([...prev, fieldId]));
243
+ setShowFieldSelector(false);
244
+ setSearchTerm('');
245
+ };
246
+
247
+ const getAvailableFields = () =>
248
+ dynamicItems.filter(
249
+ (item) =>
250
+ !visibleFieldIds.has(item.id) &&
251
+ item.title.toLowerCase().includes(searchTerm.toLowerCase()),
252
+ );
253
+
254
+ const getVisibleFields = () =>
255
+ dynamicItems.filter((item) => visibleFieldIds.has(item.id));
256
+
257
+ const shouldShowAddButton = () =>
258
+ showFieldsLimit !== 'all' &&
259
+ typeof showFieldsLimit === 'number' &&
260
+ visibleFieldIds.size < dynamicItems.length;
261
+
197
262
  return html`
198
- <div>
263
+ <div class=${styles.dynamicFieldsWrapper}>
199
264
  ${loading
200
265
  ? html`<div className=${styles.loadingText}>
201
266
  <div class=${styles.actionFormLoader}>
202
267
  <${Loader} />
203
268
  </div>
204
269
  Loading custom fields...
205
- </div> `
206
- : html` ${dynamicItems.map((el) => {
207
- const ele = props.stepDataMapping[stepId]
208
- ? props.stepDataMapping[stepId][dynamicField.id]
209
- : ({} as { objectValue: Record<string, string | number> });
210
- let fieldVal = '';
211
- const parsedVal = objectValue
212
- ? customFieldsData
213
- : (ele.objectValue as Record<string, string | number>);
214
- if (parsedVal && parsedVal[el.id]) {
215
- fieldVal = `${parsedVal[el.id]}`;
216
- }
217
- return html`
218
- <div class=${styles.dynamicFieldWrapper}>
219
- <${MultipurposeField}
220
- title=${el.title}
221
- placeholder=${getPlaceholder()}
222
- value=${fieldVal}
223
- onChange=${(val: string) => {
224
- onChange(el.id, val);
225
- }}
226
- isReadOnly=${isReadOnly}
227
- type="TEXTFIELD"
228
- isMappable=${isMappable}
229
- isEditable=${isEditable}
230
- allowTagsInText=${allowTagsInText}
231
- activityOutputData=${activityOutputData}
232
- activityOutputDataRaw=${activityOutputDataRaw}
233
- refreshRootStepData=${refreshRootStepData}
234
- fieldId=${el.id || ''}
235
- tagsTree=${tagsTree}
236
- showMenuOnLeft=${showMenuOnLeft}
237
- />
238
- </div>
239
- `;
240
- })}`}
270
+ </div>`
271
+ : html`
272
+ ${getVisibleFields().map((el) => {
273
+ const ele = props.stepDataMapping[stepId]
274
+ ? props.stepDataMapping[stepId][dynamicField.id]
275
+ : ({} as { objectValue: Record<string, string | number> });
276
+ let fieldVal = '';
277
+ const parsedVal = objectValue
278
+ ? customFieldsData
279
+ : (ele.objectValue as Record<string, string | number>);
280
+ if (parsedVal && parsedVal[el.id]) {
281
+ fieldVal = `${parsedVal[el.id]}`;
282
+ }
283
+ return html`
284
+ <div class=${styles.dynamicFieldWrapper}>
285
+ <${MultipurposeField}
286
+ title=${el.title}
287
+ placeholder=${getPlaceholder()}
288
+ value=${fieldVal}
289
+ onChange=${(val: string) => {
290
+ onChange(el.id, val);
291
+ }}
292
+ isReadOnly=${isReadOnly}
293
+ type="TEXTFIELD"
294
+ isMappable=${isMappable}
295
+ isEditable=${isEditable}
296
+ allowTagsInText=${allowTagsInText}
297
+ activityOutputData=${activityOutputData}
298
+ activityOutputDataRaw=${activityOutputDataRaw}
299
+ refreshRootStepData=${refreshRootStepData}
300
+ fieldId=${el.id || ''}
301
+ tagsTree=${tagsTree}
302
+ showMenuOnLeft=${showMenuOnLeft}
303
+ />
304
+ </div>
305
+ `;
306
+ })}
307
+ ${shouldShowAddButton() && !showFieldSelector
308
+ ? html`
309
+ <div class=${styles.addFieldButtonWrapper}>
310
+ <button
311
+ class=${styles.addFieldButton}
312
+ onClick=${() => setShowFieldSelector(true)}
313
+ type="button"
314
+ >
315
+ <span class=${styles.addIcon}>+</span>
316
+ Add Field Mapping
317
+ </button>
318
+ </div>
319
+ `
320
+ : ''}
321
+ ${showFieldSelector
322
+ ? html`
323
+ <div class=${styles.fieldSelectorWrapper}>
324
+ <div class=${styles.searchInputWrapper}>
325
+ <input
326
+ type="text"
327
+ class=${styles.searchInput}
328
+ placeholder="Search fields to add..."
329
+ value=${searchTerm}
330
+ onInput=${(e: Event) =>
331
+ setSearchTerm((e.target as HTMLInputElement).value)}
332
+ autofocus
333
+ />
334
+ <button
335
+ class=${styles.cancelButton}
336
+ onClick=${() => {
337
+ setShowFieldSelector(false);
338
+ setSearchTerm('');
339
+ }}
340
+ type="button"
341
+ >
342
+ ×
343
+ </button>
344
+ </div>
345
+ ${getAvailableFields().length > 0
346
+ ? html`
347
+ <div class=${styles.fieldDropdown}>
348
+ ${getAvailableFields().map(
349
+ (field) => html`
350
+ <div
351
+ class=${styles.fieldOption}
352
+ onClick=${() => handleFieldSelect(field.id)}
353
+ >
354
+ ${field.title}
355
+ </div>
356
+ `,
357
+ )}
358
+ </div>
359
+ `
360
+ : html`
361
+ <div class=${styles.noFieldsMessage}>
362
+ No matching fields found
363
+ </div>
364
+ `}
365
+ </div>
366
+ `
367
+ : ''}
368
+ `}
241
369
  ${!isErrorOnLoadingCustomFields
242
370
  ? ''
243
- : html`<span className=${styles.noOptions}
244
- >Could not load custom fields.
371
+ : html`<span className=${styles.noOptions}>
372
+ Could not load custom fields.
245
373
  ${false
246
374
  ? html``
247
375
  : html` <a
@@ -39,7 +39,7 @@
39
39
  .loadingText {
40
40
  font-weight: 400;
41
41
  font-size: 12px;
42
- line-height: 15px;
42
+ // line-height: 15px;
43
43
  margin-bottom: 12px;
44
44
  color: #999;
45
45
  display: flex;
@@ -65,3 +65,202 @@ a.optionsRefresh {
65
65
  cursor: pointer;
66
66
  text-decoration: none;
67
67
  }
68
+ .dynamicFieldsWrapper {
69
+ // Existing styles plus new ones for the field selector
70
+
71
+ .loadingText {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 8px;
75
+ color: #666;
76
+ font-size: 12px;
77
+ }
78
+
79
+ .actionFormLoader {
80
+ display: inline-block;
81
+ }
82
+
83
+ .dynamicFieldWrapper {
84
+ margin-bottom: 16px;
85
+ }
86
+
87
+ .noOptions {
88
+ color: #e74c3c;
89
+ font-size: 12px;
90
+ }
91
+
92
+ .optionsRefresh {
93
+ color: #3498db;
94
+ text-decoration: none;
95
+
96
+ &:hover {
97
+ text-decoration: underline;
98
+ }
99
+ }
100
+
101
+ // New styles for field limiting functionality
102
+ .addFieldButtonWrapper {
103
+ margin-top: 16px;
104
+ margin-bottom: 16px;
105
+ }
106
+
107
+ .addFieldButton {
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 8px;
111
+ padding: 12px 16px;
112
+ background: transparent;
113
+ border: 2px dashed #d1d5db;
114
+ border-radius: 2px;
115
+ color: #6b7280;
116
+ font-size: 12px;
117
+ font-weight: 500;
118
+ cursor: pointer;
119
+ transition: all 0.2s ease;
120
+ width: 100%;
121
+ justify-content: center;
122
+
123
+ &:hover {
124
+ border-color: #9ca3af;
125
+ color: #4b5563;
126
+ background: #f9fafb;
127
+ }
128
+
129
+ &:focus {
130
+ outline: none;
131
+ border-color: #3b82f6;
132
+ color: #3b82f6;
133
+ }
134
+ }
135
+
136
+ .addIcon {
137
+ font-size: 16px;
138
+ font-weight: bold;
139
+ }
140
+
141
+ .fieldSelectorWrapper {
142
+ position: relative;
143
+ margin-top: 16px;
144
+ margin-bottom: 16px;
145
+
146
+ &.dropdownTop {
147
+ // When dropdown opens upward, we need extra space above
148
+ margin-top: 220px; // Max dropdown height + buffer
149
+ margin-bottom: 16px;
150
+ }
151
+ }
152
+
153
+ .searchInputWrapper {
154
+ position: relative;
155
+ display: flex;
156
+ align-items: center;
157
+ }
158
+
159
+ .searchInput {
160
+ width: 100%;
161
+ padding: 12px 16px;
162
+ padding-right: 40px;
163
+ border: 1px solid #d1d5db;
164
+ border-radius: 2px;
165
+ font-size: 12px;
166
+ background: white;
167
+
168
+ &:focus {
169
+ outline: none;
170
+ border-color: #3b82f6;
171
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
172
+ }
173
+
174
+ &::placeholder {
175
+ color: #9ca3af;
176
+ }
177
+ }
178
+
179
+ .cancelButton {
180
+ position: absolute;
181
+ right: 12px;
182
+ background: none;
183
+ border: none;
184
+ font-size: 18px;
185
+ color: #9ca3af;
186
+ cursor: pointer;
187
+ padding: 0;
188
+ width: 20px;
189
+ height: 20px;
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+
194
+ &:hover {
195
+ color: #6b7280;
196
+ }
197
+ }
198
+
199
+ .fieldDropdown {
200
+ // position: absolute;
201
+ top: 100%;
202
+ left: 0;
203
+ right: 0;
204
+ background: white;
205
+ border: 1px solid #d1d5db;
206
+ border-radius: 2px;
207
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
208
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
209
+ max-height: 200px;
210
+ overflow-y: auto;
211
+ z-index: 1000;
212
+ margin-top: 4px;
213
+
214
+ &.fieldDropdownTop {
215
+ top: auto;
216
+ bottom: 100%;
217
+ margin-top: 0;
218
+ margin-bottom: 4px;
219
+ }
220
+ }
221
+
222
+ .fieldOption {
223
+ padding: 12px 16px;
224
+ cursor: pointer;
225
+ font-size: 11px;
226
+ border-bottom: 1px solid #f3f4f6;
227
+
228
+ &:last-child {
229
+ border-bottom: none;
230
+ }
231
+
232
+ &:hover {
233
+ background: #f9fafb;
234
+ }
235
+
236
+ &:active {
237
+ background: #f3f4f6;
238
+ }
239
+ }
240
+
241
+ .noFieldsMessage {
242
+ padding: 16px;
243
+ text-align: center;
244
+ color: #9ca3af;
245
+ font-size: 12px;
246
+ font-style: italic;
247
+ position: absolute;
248
+ top: 100%;
249
+ left: 0;
250
+ right: 0;
251
+ background: white;
252
+ border: 1px solid #d1d5db;
253
+ border-radius: 2px;
254
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
255
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
256
+ z-index: 1000;
257
+ margin-top: 4px;
258
+
259
+ &.noFieldsMessageTop {
260
+ top: auto;
261
+ bottom: 100%;
262
+ margin-top: 0;
263
+ margin-bottom: 4px;
264
+ }
265
+ }
266
+ }