@plusscommunities/pluss-feature-builder-web-d 1.0.2-beta.7 → 1.0.2-beta.9

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.
@@ -1,6 +1,6 @@
1
- import React, { useState, useEffect } from "react";
1
+ import React from "react";
2
2
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
- import { CheckBox, Text, Button } from "./index.js";
3
+ import { Text, Button } from "./index.js";
4
4
  import { BaseFieldConfig } from "./BaseFieldConfig.jsx";
5
5
  import { iconImports } from "./iconImports.js";
6
6
  import styles from "./Fields.module.css";
@@ -130,7 +130,6 @@ export const Field = (props) => {
130
130
  );
131
131
  };
132
132
 
133
- // Padlock icon component for base fields
134
133
  const PadlockIcon = () => {
135
134
  const icon = iconImports.lock;
136
135
  return (
@@ -140,39 +139,6 @@ const PadlockIcon = () => {
140
139
  );
141
140
  };
142
141
 
143
- // Field type icon component
144
- const FieldTypeIcon = ({ type }) => {
145
- const getIconForType = (type) => {
146
- switch (type) {
147
- case "title":
148
- return iconImports.heading;
149
- case "text":
150
- return iconImports.pen;
151
- case "description":
152
- return iconImports.alignLeft;
153
- case "image":
154
- return iconImports.image;
155
- case "feature-image":
156
- return iconImports.camera;
157
-
158
- case "cta":
159
- return iconImports.link;
160
- case "file":
161
- return iconImports.file;
162
- default:
163
- return iconImports.cog;
164
- }
165
- };
166
-
167
- const icon = getIconForType(type);
168
-
169
- return (
170
- <span className={styles.fieldTypeIcon}>
171
- <FontAwesomeIcon icon={icon} />
172
- </span>
173
- );
174
- };
175
-
176
142
  // Helper function to get display text for field types
177
143
  const getFieldTypeDisplay = (type) => {
178
144
  switch (type) {
@@ -188,7 +154,6 @@ const getFieldTypeDisplay = (type) => {
188
154
  return "Gallery";
189
155
  case "feature-image":
190
156
  return "Feature Image";
191
-
192
157
  case "cta":
193
158
  return "Action Button";
194
159
  case "file":
@@ -207,23 +172,22 @@ const TitleField = (props) => {
207
172
  showWarnings,
208
173
  id,
209
174
  } = props;
210
- const { placeholder, label, isRequired, helpText } = props.values;
175
+ const { placeholder, label, helpText } = props.values;
211
176
 
212
177
  return (
213
178
  <BaseFieldConfig
214
179
  label={label}
215
180
  placeholder={placeholder}
216
181
  helpText={helpText}
217
- isRequired={true} // Title fields are always required
182
+ isRequired={true}
218
183
  setLabel={setLabel}
219
- setPlaceholder={() => {}}
184
+ setPlaceholder={setPlaceholder}
220
185
  setHelpText={setHelpText}
221
- toggleIsRequired={() => {}} // Disable toggling - title fields are always required
186
+ toggleIsRequired={() => {}}
222
187
  stepErrors={stepErrors}
223
188
  showWarnings={showWarnings}
224
189
  id={id}
225
190
  fieldType="title"
226
- id={id} // Fix: pass the field ID
227
191
  />
228
192
  );
229
193
  };
@@ -235,25 +199,25 @@ const TextField = (props) => {
235
199
  setHelpText,
236
200
  stepErrors,
237
201
  showWarnings,
202
+ toggleIsRequired,
238
203
  id,
239
204
  } = props;
240
- const { placeholder, label, isRequired, helpText } = props.values;
205
+ const { placeholder, label, helpText, isRequired } = props.values;
241
206
 
242
207
  return (
243
208
  <BaseFieldConfig
244
209
  label={label}
245
210
  placeholder={placeholder}
246
211
  helpText={helpText}
247
- isRequired={true} // Text fields are always required
212
+ isRequired={isRequired}
248
213
  setLabel={setLabel}
249
- setPlaceholder={() => {}}
214
+ setPlaceholder={setPlaceholder}
250
215
  setHelpText={setHelpText}
251
- toggleIsRequired={() => {}} // Disable toggling - text fields are always required
216
+ toggleIsRequired={toggleIsRequired}
252
217
  stepErrors={stepErrors}
253
218
  showWarnings={showWarnings}
254
219
  id={id}
255
220
  fieldType="text"
256
- id={id} // Fix: pass the field ID
257
221
  />
258
222
  );
259
223
  };
@@ -267,7 +231,7 @@ const DescriptionField = (props) => {
267
231
  stepErrors,
268
232
  showWarnings,
269
233
  setUseAsSummary,
270
- id, // Get ID from root props
234
+ id,
271
235
  } = props;
272
236
  const { placeholder, label, isRequired, helpText, useAsSummary } =
273
237
  props.values;
@@ -279,7 +243,7 @@ const DescriptionField = (props) => {
279
243
  helpText={helpText}
280
244
  isRequired={isRequired}
281
245
  setLabel={setLabel}
282
- setPlaceholder={() => {}}
246
+ setPlaceholder={setPlaceholder}
283
247
  setHelpText={setHelpText}
284
248
  toggleIsRequired={toggleIsRequired}
285
249
  stepErrors={stepErrors}
@@ -301,7 +265,7 @@ const ImageField = (props) => {
301
265
  toggleAllowCaption,
302
266
  stepErrors,
303
267
  showWarnings,
304
- id, // Get ID from root props
268
+ id,
305
269
  } = props;
306
270
  const { label, isRequired, placeholder, allowCaption, helpText } =
307
271
  props.values;
@@ -321,20 +285,14 @@ const ImageField = (props) => {
321
285
  toggleAllowCaption={toggleAllowCaption}
322
286
  stepErrors={stepErrors}
323
287
  showWarnings={showWarnings}
324
- id={id} // Fix: pass the field ID
288
+ id={id}
325
289
  fieldType="image"
326
290
  />
327
291
  );
328
292
  };
329
293
 
330
294
  const FeatureImageField = (props) => {
331
- const {
332
- setLabel,
333
- setHelpText,
334
- stepErrors,
335
- showWarnings,
336
- id, // Get ID from root props
337
- } = props;
295
+ const { setLabel, setHelpText, stepErrors, showWarnings, id } = props;
338
296
  const { label, placeholder, helpText } = props.values;
339
297
 
340
298
  return (
@@ -342,14 +300,14 @@ const FeatureImageField = (props) => {
342
300
  label={label}
343
301
  placeholder={placeholder}
344
302
  helpText={helpText}
345
- isRequired={true} // Feature image fields are always required
303
+ isRequired={true}
346
304
  setLabel={setLabel}
347
305
  setPlaceholder={() => {}}
348
306
  setHelpText={setHelpText}
349
- toggleIsRequired={() => {}} // Disable toggling - feature image fields are always required
307
+ toggleIsRequired={() => {}}
350
308
  stepErrors={stepErrors}
351
309
  showWarnings={showWarnings}
352
- id={id} // Fix: pass the field ID
310
+ id={id}
353
311
  fieldType="feature-image"
354
312
  customLabel="Label"
355
313
  />
@@ -367,7 +325,7 @@ const GalleryField = (props) => {
367
325
  setAllowedTypes,
368
326
  stepErrors,
369
327
  showWarnings,
370
- id, // Get ID from root props
328
+ id,
371
329
  } = props;
372
330
  const {
373
331
  label,
@@ -415,9 +373,9 @@ const CTAField = (props) => {
415
373
  setPlaceholder,
416
374
  stepErrors,
417
375
  showWarnings,
376
+ id,
418
377
  } = props;
419
378
  const { label, isRequired, helpText, placeholder } = props.values;
420
- const { id } = props; // Get ID from root props
421
379
 
422
380
  return (
423
381
  <BaseFieldConfig
@@ -426,21 +384,26 @@ const CTAField = (props) => {
426
384
  helpText={helpText}
427
385
  isRequired={isRequired}
428
386
  setLabel={setLabel}
429
- setPlaceholder={() => {}}
387
+ setPlaceholder={setPlaceholder}
430
388
  setHelpText={setHelpText}
431
389
  toggleIsRequired={toggleIsRequired}
432
390
  stepErrors={stepErrors}
433
391
  showWarnings={showWarnings}
434
- id={id} // Fix: pass the field ID
392
+ id={id}
435
393
  fieldType="cta"
436
394
  />
437
395
  );
438
396
  };
439
397
 
440
398
  const FileField = (props) => {
441
- const { toggleIsRequired, setLabel, setHelpText, stepErrors, showWarnings } =
442
- props;
443
- const { id } = props; // Get ID from root props
399
+ const {
400
+ toggleIsRequired,
401
+ setLabel,
402
+ setHelpText,
403
+ stepErrors,
404
+ showWarnings,
405
+ id,
406
+ } = props;
444
407
  const { label, isRequired, helpText } = props.values;
445
408
 
446
409
  return (
@@ -168,8 +168,8 @@ const ListingEditor = ({ mode = "create", listingId, onSuccess, onCancel }) => {
168
168
 
169
169
  // formData always has structure { fields: {...} } after our fixes
170
170
  const submissionData = isEditMode
171
- ? { id: listingId, fields: formData.fields }
172
- : { fields: formData.fields };
171
+ ? { id: listingId, fields: formData.fields, site: auth.site }
172
+ : { fields: formData.fields, site: auth.site };
173
173
 
174
174
  const actionPromise = dispatch(
175
175
  isEditMode ? editListing(submissionData) : createListing(submissionData),
@@ -84,7 +84,7 @@ const SideBarInner = (props) => {
84
84
 
85
85
  // Build sidebar items based on mode
86
86
  const buildSidebarItems = () => {
87
- const isWizardMode = mode === "create" || mode === "edit";
87
+ const isWizardMode = mode === "create";
88
88
 
89
89
  return steps.map((step, index) => {
90
90
  const isCompleted = selectIsStepComplete(step.key);
@@ -109,8 +109,8 @@ const SideBarInner = (props) => {
109
109
  isFontAwesome: true,
110
110
  // Enhanced completion indicator
111
111
  completed: isCompleted,
112
- // Disable all navigation in wizard mode
113
- disabled: isWizardMode || (mode === "create" && !isAccessible),
112
+ // Disable all navigation in create mode, otherwise respect accessibility
113
+ disabled: mode === "create" || !isAccessible,
114
114
  };
115
115
 
116
116
  return itemProps;
@@ -137,7 +137,7 @@ const SideBarInner = (props) => {
137
137
 
138
138
  // Add effect to manually attach click handlers since HubSidebar might not use onclick properly
139
139
  useEffect(() => {
140
- const isWizardMode = mode === "create" || mode === "edit";
140
+ const isWizardMode = mode === "create";
141
141
 
142
142
  const attachClickHandlers = () => {
143
143
  const stepsWithUrls = [
@@ -14,27 +14,26 @@ const ListingFileInput = ({
14
14
  errorMessage,
15
15
  disabled = false,
16
16
  }) => {
17
- const fileInputRef = useRef(null);
18
- const [internalFiles, setInternalFiles] = useState([]);
17
+ const [inputs, setInputs] = useState([{ id: 0, fileUrl: null }]);
18
+ const nextIdRef = useRef(1);
19
19
 
20
- // Normalize file data to enhanced structure - always handle as multiple files
20
+ // Initialize inputs from value prop - map each existing file to an input
21
21
  useEffect(() => {
22
22
  if (value) {
23
+ let fileData = [];
24
+
23
25
  // Handle multiple files - could be array of URLs or array of file objects
24
26
  if (Array.isArray(value)) {
25
- const normalizedFiles = value
27
+ fileData = value
26
28
  .map((file) => {
27
29
  if (typeof file === "string") {
28
- // Backward compatibility: array of URLs
29
30
  const fileName = file.split("/").pop() || "Unknown File";
30
31
  return {
31
32
  url: file,
32
- name: fileName.replace(/\.[^/.]+$/, ""), // Remove extension
33
+ name: fileName.replace(/\.[^/.]+$/, ""),
33
34
  originalName: fileName,
34
- uploading: false,
35
35
  };
36
36
  } else if (file && typeof file === "object" && file.url) {
37
- // Enhanced structure
38
37
  return {
39
38
  url: file.url,
40
39
  name: file.name || file.originalName || "Unknown File",
@@ -42,27 +41,22 @@ const ListingFileInput = ({
42
41
  file.originalName ||
43
42
  file.url.split("/").pop() ||
44
43
  "Unknown File",
45
- uploading: false,
46
44
  };
47
45
  }
48
46
  return null;
49
47
  })
50
48
  .filter(Boolean);
51
- setInternalFiles(normalizedFiles);
52
49
  } else {
53
- // Handle single file values - normalize to array
50
+ // Handle single file values
54
51
  let singleFile = null;
55
52
  if (typeof value === "string") {
56
- // Backward compatibility: single URL
57
53
  const fileName = value.split("/").pop() || "Unknown File";
58
54
  singleFile = {
59
55
  url: value,
60
56
  name: fileName.replace(/\.[^/.]+$/, ""),
61
57
  originalName: fileName,
62
- uploading: false,
63
58
  };
64
59
  } else if (value && typeof value === "object" && value.url) {
65
- // Enhanced structure - preserve empty name if explicitly set
66
60
  singleFile = {
67
61
  url: value.url,
68
62
  name:
@@ -73,60 +67,104 @@ const ListingFileInput = ({
73
67
  value.originalName ||
74
68
  value.url.split("/").pop() ||
75
69
  "Unknown File",
76
- uploading: false,
77
70
  };
78
71
  }
79
- setInternalFiles(singleFile ? [singleFile] : []);
72
+ fileData = singleFile ? [singleFile] : [];
80
73
  }
74
+
75
+ // Create inputs for each existing file
76
+ const existingInputs = fileData.map((file, index) => ({
77
+ id: index,
78
+ fileUrl: file.url,
79
+ name: file.name,
80
+ originalName: file.originalName,
81
+ }));
82
+ // Add one empty input at the end
83
+ nextIdRef.current = fileData.length;
84
+ setInputs([...existingInputs, { id: nextIdRef.current, fileUrl: null }]);
85
+ nextIdRef.current++;
81
86
  } else {
82
- setInternalFiles([]);
87
+ setInputs([{ id: 0, fileUrl: null }]);
83
88
  }
84
89
  }, [value]);
85
90
 
86
91
  // Handle new file upload from FileInput component
87
- const handleFileUpload = (uploadedUrl) => {
88
- // Handle case where uploadedUrl might be undefined
92
+ const handleFileUpload = (inputId, uploadedUrl) => {
89
93
  if (!uploadedUrl) {
90
94
  return;
91
95
  }
92
96
 
93
97
  const fileName = uploadedUrl.split("/").pop() || "Unknown File";
94
- const newFile = {
95
- url: uploadedUrl,
96
- name: "", // Start with empty name as requested
97
- originalName: fileName,
98
- uploading: false,
99
- };
100
-
101
- // Always add new file to existing array for multiple file support
102
- const updatedFiles = [...internalFiles, newFile];
103
- setInternalFiles(updatedFiles);
104
- notifyParent(updatedFiles);
98
+
99
+ // Find the input that triggered this upload and update it
100
+ setInputs((prevInputs) => {
101
+ const updatedInputs = prevInputs.map((input) => {
102
+ if (input.id === inputId) {
103
+ return {
104
+ ...input,
105
+ fileUrl: uploadedUrl,
106
+ name: "",
107
+ originalName: fileName,
108
+ };
109
+ }
110
+ return input;
111
+ });
112
+
113
+ // Add a new empty input for the next upload
114
+ updatedInputs.push({ id: nextIdRef.current, fileUrl: null });
115
+ nextIdRef.current++;
116
+
117
+ return updatedInputs;
118
+ });
105
119
  };
106
120
 
107
121
  // Handle file name change
108
122
  const handleNameChange = (url, newName) => {
109
- const updatedFiles = internalFiles.map((file) =>
110
- file.url === url ? { ...file, name: newName } : file,
123
+ setInputs((prevInputs) =>
124
+ prevInputs.map((input) =>
125
+ input.fileUrl === url ? { ...input, name: newName } : input,
126
+ ),
111
127
  );
112
- setInternalFiles(updatedFiles);
113
- notifyParent(updatedFiles);
114
128
  };
115
129
 
116
130
  // Handle file removal
117
131
  const handleFileRemove = (url) => {
118
- const updatedFiles = internalFiles.filter((file) => file.url !== url);
119
- setInternalFiles(updatedFiles);
120
- notifyParent(updatedFiles);
132
+ setInputs((prevInputs) => {
133
+ return prevInputs.map((input) => {
134
+ if (input.fileUrl === url) {
135
+ return { ...input, fileUrl: null };
136
+ }
137
+ return input;
138
+ });
139
+ });
121
140
  };
122
141
 
123
- // Notify parent component of changes
124
- const notifyParent = (files) => {
125
- if (onChange) {
126
- // Always return array of files for multiple file support
142
+ // Get all uploaded files from inputs
143
+ const getUploadedFiles = () => {
144
+ return inputs
145
+ .filter((input) => input.fileUrl !== null)
146
+ .map((input) => ({
147
+ url: input.fileUrl,
148
+ name: input.name,
149
+ originalName: input.originalName,
150
+ }));
151
+ };
152
+
153
+ // Track previous uploaded files to prevent infinite loops
154
+ const prevFilesRef = useRef([]);
155
+
156
+ // Sync with parent when uploaded files actually change
157
+ useEffect(() => {
158
+ const files = getUploadedFiles();
159
+ const currentUrls = files.map((f) => f.url).join(",");
160
+ const prevUrls = prevFilesRef.current.map((f) => f.url).join(",");
161
+
162
+ // Only call onChange if the file list actually changed
163
+ if (currentUrls !== prevUrls && onChange) {
164
+ prevFilesRef.current = files;
127
165
  onChange(field.id, files);
128
166
  }
129
- };
167
+ }, [inputs]);
130
168
 
131
169
  // Define acceptable file types for documents and common formats
132
170
  const acceptTypes = {
@@ -145,7 +183,11 @@ const ListingFileInput = ({
145
183
  "image/png": [".png"],
146
184
  };
147
185
 
148
- // Always show drop zone regardless of existing files
186
+ // Get the last empty input (the one to show)
187
+ const emptyInput = inputs.find((input) => input.fileUrl === null);
188
+
189
+ // Get uploaded files for display
190
+ const uploadedFiles = getUploadedFiles();
149
191
 
150
192
  return (
151
193
  <div className={styles.listingFileInput}>
@@ -169,25 +211,26 @@ const ListingFileInput = ({
169
211
  )}
170
212
  </Text>
171
213
 
172
- {/* Drop zone */}
214
+ {/* Drop zone - always show exactly one empty input */}
173
215
  <div
174
216
  className={`${styles.listingFileInput__dropZone} ${showError ? styles["listingFileInput__dropZone--error"] : ""}`}
175
217
  >
176
- <FileInput
177
- ref={fileInputRef}
178
- className="imageInputOuter-single"
179
- refreshCallback={handleFileUpload}
180
- hasDefault={null}
181
- accept={acceptTypes}
182
- multiple={false} // Always handle one file at a time in the upload callback
183
- disabled={disabled}
184
- />
218
+ {emptyInput && (
219
+ <FileInput
220
+ key={emptyInput.id}
221
+ refreshCallback={(url) => handleFileUpload(emptyInput.id, url)}
222
+ hasDefault={null}
223
+ accept={acceptTypes}
224
+ multiple={false}
225
+ disabled={disabled}
226
+ />
227
+ )}
185
228
  </div>
186
229
 
187
230
  {/* File list */}
188
- {internalFiles.length > 0 && (
231
+ {uploadedFiles.length > 0 && (
189
232
  <div className={styles.listingFileInput__fileList}>
190
- {internalFiles.map((file) => (
233
+ {uploadedFiles.map((file) => (
191
234
  <FileListItem
192
235
  key={file.url}
193
236
  file={file}
@@ -199,31 +242,6 @@ const ListingFileInput = ({
199
242
  </div>
200
243
  )}
201
244
 
202
- {/* Add more files button - always show when files exist */}
203
- {internalFiles.length > 0 && (
204
- <div>
205
- <FileInput
206
- ref={fileInputRef}
207
- className="imageInputOuter-single"
208
- refreshCallback={handleFileUpload}
209
- hasDefault={null}
210
- accept={acceptTypes}
211
- multiple={false}
212
- disabled={disabled}
213
- customButton={
214
- <Button
215
- buttonType="secondary"
216
- className={styles.listingFileInput__addMoreButton}
217
- disabled={disabled}
218
- >
219
- <FontAwesomeIcon icon={iconImports.plus} />
220
- Add More Files
221
- </Button>
222
- }
223
- />
224
- </div>
225
- )}
226
-
227
245
  {showError && errorMessage && (
228
246
  <Text
229
247
  type="help"
@@ -38,9 +38,7 @@
38
38
  display: flex;
39
39
  align-items: center;
40
40
  gap: 0.75rem;
41
- padding: 0.75rem;
42
41
  background-color: var(--bg-white);
43
- margin-bottom: 0.5rem;
44
42
  transition: all var(--transition-base) ease-in-out;
45
43
  }
46
44
 
@@ -50,9 +48,8 @@
50
48
 
51
49
  .fileListItem__icon {
52
50
  color: var(--text-bluegrey);
53
- font-size: var(--font-size-base);
51
+ font-size: 2rem;
54
52
  flex-shrink: 0;
55
- width: 1.5rem;
56
53
  text-align: center;
57
54
  }
58
55
 
@@ -3,6 +3,7 @@
3
3
  /* Gallery container styles */
4
4
  .galleryContainer {
5
5
  margin-top: 0.75rem;
6
+ max-width: 200px;
6
7
  }
7
8
 
8
9
  /* Error border wrapper */
@@ -20,7 +21,7 @@
20
21
 
21
22
  .imageGrid {
22
23
  display: grid;
23
- grid-template-columns: repeat(var(--columns, 4), 1fr);
24
+ grid-template-columns: repeat(4, 160px);
24
25
  gap: 1rem;
25
26
  margin-top: 0.5rem;
26
27
  }
Binary file
Binary file
Binary file
Binary file
@@ -86,7 +86,7 @@ const DEFAULT_NEW_FIELD = {
86
86
  allowCaption: false, // Default to not allowing captions for image fields
87
87
  useAsSummary: false, // Default to not using as summary for description fields
88
88
  minImages: 1, // Gallery-specific default
89
- maxImages: 10, // Gallery-specific default
89
+ maxImages: 16, // Gallery-specific default
90
90
  maxFileSize: "5MB", // Gallery-specific default
91
91
  allowedTypes: [], // Gallery-specific default
92
92
  },
@@ -138,7 +138,7 @@ function createNewField(id, type = "text", existingFields = []) {
138
138
  baseField.values.label = "Image Gallery";
139
139
  baseField.values.helpText = "Upload multiple images to create a gallery";
140
140
  baseField.values.minImages = 1;
141
- baseField.values.maxImages = 10;
141
+ baseField.values.maxImages = 16;
142
142
  baseField.values.maxFileSize = "5MB";
143
143
  baseField.values.allowedTypes = ["image/jpeg", "image/png", "image/webp"];
144
144
  const {
@@ -277,8 +277,11 @@ const formReducer = (state = INITIAL_FORM_STATE, action) => {
277
277
  switch (type) {
278
278
  case formActionTypes.SET_INITIAL_VALUES: {
279
279
  // The actual definition data is in payload.featureDefinition.definition
280
- const definitionWrapper = (payload && payload.featureDefinition) || payload;
281
- const definition = (definitionWrapper && definitionWrapper.definition) || definitionWrapper;
280
+ const definitionWrapper =
281
+ (payload && payload.featureDefinition) || payload;
282
+ const definition =
283
+ (definitionWrapper && definitionWrapper.definition) ||
284
+ definitionWrapper;
282
285
 
283
286
  // Validate and map definition data to form state structure
284
287
  const mappedValues = {
@@ -513,8 +516,11 @@ const definitionReducer = (state = INITIAL_STATE.definition, action) => {
513
516
  const featureDefinitionWrapper =
514
517
  (data && data.featureDefinition) || data;
515
518
  definition =
516
- (featureDefinitionWrapper && featureDefinitionWrapper.definition) || featureDefinitionWrapper;
517
- definitionId = (featureDefinitionWrapper && featureDefinitionWrapper.id) || values.featureId;
519
+ (featureDefinitionWrapper && featureDefinitionWrapper.definition) ||
520
+ featureDefinitionWrapper;
521
+ definitionId =
522
+ (featureDefinitionWrapper && featureDefinitionWrapper.id) ||
523
+ values.featureId;
518
524
 
519
525
  // Ensure fields array exists and preserves order property
520
526
  if (definition?.fields) {