react-jsonschema-form-extras 1.0.0 → 1.1.0-alpha.183650

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/lib/DraftRte.js CHANGED
@@ -74,15 +74,15 @@ function mapFromObject(data, mapping, defVal) {
74
74
  return agg;
75
75
  }, defVal);
76
76
  }
77
- /**
78
- *
79
- * @param {*} data
80
- * @param {*} mapping
81
- * Mapped object is converted to the object mapping takes
77
+ /**
78
+ *
79
+ * @param {*} data
80
+ * @param {*} mapping
81
+ * Mapped object is converted to the object mapping takes
82
82
  */
83
83
  function mapFromSchema(data, mapping) {
84
- /* if (isEmpty(data)) {
85
- return;
84
+ /* if (isEmpty(data)) {
85
+ return;
86
86
  } */
87
87
  if (!mapping || mapping === null) {
88
88
  return data;
@@ -95,11 +95,11 @@ function mapFromSchema(data, mapping) {
95
95
  }
96
96
  }
97
97
 
98
- /**
99
- *
100
- * @param {*} data
101
- * @param {*} mapping
102
- * prepare the String to display
98
+ /**
99
+ *
100
+ * @param {*} data
101
+ * @param {*} mapping
102
+ * prepare the String to display
103
103
  */
104
104
  function optionToString(fields, separator) {
105
105
  return function (option) {
@@ -119,11 +119,11 @@ function optionToString(fields, separator) {
119
119
  }, "");
120
120
  };
121
121
  }
122
- /**
123
- *
124
- * @param {*} data
125
- * @param {*} mapping
126
- * maping the label
122
+ /**
123
+ *
124
+ * @param {*} data
125
+ * @param {*} mapping
126
+ * maping the label
127
127
  */
128
128
  function mapLabelKey(labelKey) {
129
129
  if (Array.isArray(labelKey)) {
@@ -140,10 +140,10 @@ function mapLabelKey(labelKey) {
140
140
  var DraftRTE = function (_Component) {
141
141
  _inherits(DraftRTE, _Component);
142
142
 
143
- /**
144
- *
145
- * @param {*} props
146
- * Currently only supports HTML
143
+ /**
144
+ *
145
+ * @param {*} props
146
+ * Currently only supports HTML
147
147
  */
148
148
  function DraftRTE(props) {
149
149
  _classCallCheck(this, DraftRTE);
@@ -183,24 +183,24 @@ var DraftRTE = function (_Component) {
183
183
  };
184
184
  return _this;
185
185
  }
186
- /**
187
- * updates formData by calling parent's onChange function with current html content
186
+ /**
187
+ * updates formData by calling parent's onChange function with current html content
188
188
  */
189
189
 
190
190
 
191
- /**
192
- * handles editor's onChange
193
- * handles logic to update form data based on props supplied to the component
191
+ /**
192
+ * handles editor's onChange
193
+ * handles logic to update form data based on props supplied to the component
194
194
  */
195
195
  //will only be executed if debounce is present
196
196
 
197
- /**
198
- * handles the logic to update formData on blur
197
+ /**
198
+ * handles the logic to update formData on blur
199
199
  */
200
200
 
201
201
 
202
- /**
203
- * handles the logic to load the suggestions on the time of focus
202
+ /**
203
+ * handles the logic to load the suggestions on the time of focus
204
204
  */
205
205
 
206
206
 
@@ -208,8 +208,8 @@ var DraftRTE = function (_Component) {
208
208
  key: "render",
209
209
 
210
210
 
211
- /**
212
- * react render function
211
+ /**
212
+ * react render function
213
213
  */
214
214
  value: function render() {
215
215
  var _this2 = this;
@@ -4,8 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
 
7
- var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
8
-
9
7
  var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
10
8
 
11
9
  var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
@@ -43,12 +41,12 @@ function defaultSearch(url, query) {
43
41
  });
44
42
  }
45
43
 
46
- /**
47
- * MultiTypeaheadField - A custom multi-select dropdown component with search capability.
48
- * Supports both static options and URL-based options with search functionality.
49
- * Compatible with react-jsonschema-form
44
+ /**
45
+ * MultiTypeaheadField - A custom multi-select dropdown component with search capability.
46
+ * Supports both static options and URL-based options with search functionality.
47
+ * Compatible with react-jsonschema-form
50
48
  */
51
- // Styled components for MUI v5 - converted from original withStyles
49
+ // Styled components for MUI v5
52
50
  var StyledContainer = (0, _styles.styled)("div")({
53
51
  position: "relative",
54
52
  width: "100%"
@@ -102,9 +100,9 @@ var StyledChipContainer = (0, _styles.styled)("div")({
102
100
  gap: "4px",
103
101
  alignItems: "center",
104
102
  overflow: "hidden",
105
- flex: "1 1 auto", // Allow container to grow and shrink as needed
106
- minWidth: 0, // Allow shrinking below content size
107
- minHeight: "20px", // Reduce minimum height for better alignment
103
+ flex: "1 1 auto",
104
+ minWidth: 0,
105
+ minHeight: "20px",
108
106
  paddingTop: "2px",
109
107
  paddingBottom: "2px"
110
108
  });
@@ -142,10 +140,10 @@ var StyledTextField = (0, _styles.styled)(_material.TextField)({
142
140
  "& .MuiOutlinedInput-root": {
143
141
  paddingTop: 8,
144
142
  paddingBottom: 8,
145
- minHeight: 48, // Ensure consistent height
146
- alignItems: "center", // Center align for better appearance
147
- display: "flex", // Ensure flex display
148
- flexWrap: "wrap", // Allow wrapping when needed
143
+ minHeight: 48,
144
+ alignItems: "center",
145
+ display: "flex",
146
+ flexWrap: "wrap",
149
147
  "& .MuiChip-root": {
150
148
  marginTop: 2,
151
149
  marginBottom: 2
@@ -153,19 +151,19 @@ var StyledTextField = (0, _styles.styled)(_material.TextField)({
153
151
  "& .MuiInputBase-input": {
154
152
  paddingTop: 6,
155
153
  paddingBottom: 6,
156
- flex: "1 1 120px", // Allow input to take remaining space with minimum width
157
- minWidth: "120px", // Ensure minimum input width
154
+ flex: "1 1 120px",
155
+ minWidth: "120px",
158
156
  fontFamily: "Mulish",
159
157
  fontWeight: 400,
160
158
  letterSpacing: "0.15px",
161
159
  fontSize: "16px",
162
160
  lineHeight: "12px",
163
- boxSizing: "border-box", // Ensure consistent box model
164
- color: "#003B5C", // Set visible text color for user input
161
+ boxSizing: "border-box",
162
+ color: "#003B5C",
165
163
  "&::placeholder": {
166
164
  color: "#003B5CBF",
167
- opacity: 1, // Keep placeholder visible
168
- display: "block" // Ensure placeholder is always displayed
165
+ opacity: 1,
166
+ display: "block"
169
167
  }
170
168
  }
171
169
  },
@@ -191,10 +189,10 @@ var StyledClearButton = (0, _styles.styled)(_material.IconButton)({
191
189
  }
192
190
  });
193
191
 
194
- /**
195
- * MultiTypeaheadField - A custom multi-select dropdown component with search capability.
196
- * Supports both static options and URL-based options with search functionality.
197
- * Compatible with react-jsonschema-form and MUI v5
192
+ /**
193
+ * MultiTypeaheadField - A custom multi-select dropdown component with search capability.
194
+ * Supports both static options and URL-based options with search functionality.
195
+ * Compatible with react-jsonschema-form and MUI v5
198
196
  */
199
197
  function MultiTypeaheadField(props) {
200
198
  var _useState = (0, _react.useState)(""),
@@ -217,6 +215,11 @@ function MultiTypeaheadField(props) {
217
215
  isOpen = _useState8[0],
218
216
  setIsOpen = _useState8[1];
219
217
 
218
+ var _useState9 = (0, _react.useState)(false),
219
+ _useState10 = _slicedToArray(_useState9, 2),
220
+ isTyping = _useState10[0],
221
+ setIsTyping = _useState10[1];
222
+
220
223
  var containerRef = (0, _react.useRef)(null);
221
224
  var inputRef = (0, _react.useRef)(null);
222
225
 
@@ -234,7 +237,7 @@ function MultiTypeaheadField(props) {
234
237
  queryKey = _multiTypeaheadConfig === undefined ? "query" : _multiTypeaheadConfig,
235
238
  optionsPath = multiTypeaheadConfig.optionsPath;
236
239
 
237
- // Debounced fetch function
240
+ // Fetch options (debounced)
238
241
 
239
242
  var fetchOptions = (0, _react.useCallback)(function (query) {
240
243
  if (!url || !query.trim()) {
@@ -244,28 +247,47 @@ function MultiTypeaheadField(props) {
244
247
 
245
248
  setLoading(true);
246
249
 
247
- // Use custom search function if provided, otherwise use defaultSearch
250
+ // Use custom search function if provided
248
251
  var searchFn = typeof customSearch === "function" ? customSearch : defaultSearch;
249
252
 
250
253
  searchFn(url, query, queryKey).then(function (data) {
251
- // Extract options from response using optionsPath if provided
254
+ // Extract options from response
252
255
  var newOptions = void 0;
253
256
  if (optionsPath && typeof optionsPath === "string") {
254
257
  newOptions = (0, _selectn2.default)(optionsPath, data);
255
258
  newOptions = Array.isArray(newOptions) ? newOptions : [];
256
259
  } else {
257
- // Always expect data to be an array if no optionsPath specified
260
+ // Expect data to be an array if no optionsPath specified
258
261
  newOptions = Array.isArray(data) ? data : [];
259
262
  }
260
263
 
261
- setOptions(newOptions);
264
+ // Transform options using valueKeys
265
+ var valueKeys = multiTypeaheadConfig.valueKeys || ["value"];
266
+ var transformedOptions = newOptions.map(function (option) {
267
+ if (typeof option === "string") {
268
+ return option;
269
+ }
270
+ // Always create an object with specified valueKeys
271
+ var valueObj = {};
272
+ valueKeys.forEach(function (key) {
273
+ var value = (0, _selectn2.default)(key, option);
274
+ if (value !== undefined) {
275
+ // Use the last part of the key chain instead of the full key
276
+ var lastKeyPart = key.split(".").pop();
277
+ valueObj[lastKeyPart] = value;
278
+ }
279
+ });
280
+ return valueObj;
281
+ });
282
+
283
+ setOptions(transformedOptions);
262
284
  }).catch(function (error) {
263
285
  console.error("Error fetching options:", error);
264
286
  setOptions([]);
265
287
  }).finally(function () {
266
288
  setLoading(false);
267
289
  });
268
- }, [url, customSearch, queryKey, optionsPath]);
290
+ }, [url, customSearch, queryKey, optionsPath, multiTypeaheadConfig.valueKeys]);
269
291
 
270
292
  // Debounce the fetch with useEffect
271
293
  (0, _react.useEffect)(function () {
@@ -273,9 +295,19 @@ function MultiTypeaheadField(props) {
273
295
  return;
274
296
  }
275
297
 
298
+ // When inputValue changes, user is typing
299
+ if (inputValue.trim()) {
300
+ setIsTyping(true);
301
+ setLoading(false);
302
+ }
303
+
276
304
  var timeoutId = setTimeout(function () {
277
- fetchOptions(inputValue);
278
- }, 300);
305
+ if (inputValue.trim()) {
306
+ setIsTyping(false);
307
+ setLoading(true);
308
+ fetchOptions(inputValue);
309
+ }
310
+ }, 600);
279
311
 
280
312
  return function () {
281
313
  return clearTimeout(timeoutId);
@@ -285,85 +317,66 @@ function MultiTypeaheadField(props) {
285
317
  // Initialize options for static data
286
318
  (0, _react.useEffect)(function () {
287
319
  if (staticOptions) {
288
- setOptions(staticOptions);
320
+ // Transform static options using valueKeys immediately
321
+ var valueKeys = multiTypeaheadConfig.valueKeys || ["value"];
322
+ var transformedOptions = staticOptions.map(function (option) {
323
+ if (typeof option === "string") {
324
+ return option;
325
+ }
326
+ // Always create an object with specified valueKeys
327
+ var valueObj = {};
328
+ valueKeys.forEach(function (key) {
329
+ var value = (0, _selectn2.default)(key, option);
330
+ if (value !== undefined) {
331
+ // Use the last part of the key chain instead of the full key
332
+ var lastKeyPart = key.split(".").pop();
333
+ valueObj[lastKeyPart] = value;
334
+ }
335
+ });
336
+ return valueObj;
337
+ });
338
+ setOptions(transformedOptions);
289
339
  } else if (url) {
290
340
  setOptions([]);
291
341
  }
292
- }, [staticOptions, url]);
342
+ }, [staticOptions, url, multiTypeaheadConfig.valueKeys]);
293
343
 
294
- var getOptionLabel = (0, _react.useCallback)(function (option) {
295
- var labelTemplate = multiTypeaheadConfig.labelTemplate;
296
-
297
- if (typeof option === "string") {
298
- return option;
299
- }
300
-
301
- // Always use labelTemplate if provided
302
- if (labelTemplate && typeof labelTemplate === "string") {
303
- return labelTemplate.replace(/\{([^}]+)\}/g, function (match, path) {
304
- var value = (0, _selectn2.default)(path, option);
305
- return value != null ? String(value) : "";
306
- });
344
+ // Common template rendering logic
345
+ var renderTemplate = (0, _react.useCallback)(function (template, obj) {
346
+ if (!template || typeof template !== "string") {
347
+ return "";
307
348
  }
349
+ return template.replace(/\{([^}]+)\}/g, function (match, path) {
350
+ var value = (0, _selectn2.default)(path, obj);
351
+ return value != null ? String(value) : "";
352
+ });
353
+ }, []);
308
354
 
309
- // Fallback to common label fields
310
- return option.label || option.name || option.title || String(option);
311
- }, [multiTypeaheadConfig.labelTemplate]);
312
-
313
- var getOptionValue = (0, _react.useCallback)(function (option) {
314
- var valueKeys = multiTypeaheadConfig.valueKeys || ["value"];
315
-
355
+ // Dropdown label rendering (options are already transformed)
356
+ var getOptionLabel = (0, _react.useCallback)(function (option) {
316
357
  if (typeof option === "string") {
317
358
  return option;
318
359
  }
319
360
 
320
- // Always create an object with specified valueKeys
321
- var valueObj = {};
322
- valueKeys.forEach(function (key) {
323
- var value = (0, _selectn2.default)(key, option);
324
- if (value !== undefined) {
325
- // Use the last part of the key chain instead of the full key
326
- var lastKeyPart = key.split(".").pop();
327
- valueObj[lastKeyPart] = value;
328
- }
329
- });
330
- return valueObj;
331
- }, [multiTypeaheadConfig.valueKeys]);
361
+ var template = multiTypeaheadConfig.labelTemplate;
362
+ if (template) {
363
+ return renderTemplate(template, option);
364
+ }
365
+ return "";
366
+ }, [multiTypeaheadConfig.labelTemplate, renderTemplate]);
332
367
 
333
368
  var getSelectedOptions = (0, _react.useCallback)(function () {
334
369
  var selectedOptions = [];
335
370
 
336
371
  formData.forEach(function (value) {
337
- var option = options.find(function (opt) {
338
- var optValue = getOptionValue(opt);
339
-
340
- // Handle object comparison for complex values
341
- if ((typeof value === "undefined" ? "undefined" : _typeof(value)) === "object" && (typeof optValue === "undefined" ? "undefined" : _typeof(optValue)) === "object") {
342
- return JSON.stringify(optValue) === JSON.stringify(value);
343
- }
344
-
345
- // Otherwise do string comparison
346
- return String(optValue) === String(value);
347
- });
348
-
349
- if (option) {
350
- selectedOptions.push(option);
351
- } else {
352
- // If option not found in current options, create a representation
353
- // This ensures selected values persist even when not in current search results
354
- if ((typeof value === "undefined" ? "undefined" : _typeof(value)) === "object") {
355
- // For object values, create a representation that can be displayed
356
- var _label = getOptionLabel(value);
357
- selectedOptions.push(_extends({}, value, { _displayLabel: _label }));
358
- } else {
359
- // For string values, create a simple option object
360
- selectedOptions.push({ label: String(value), value: value });
361
- }
362
- }
372
+ // For existing formData values, we don't need to find them in current options
373
+ // since they may not be present in current search results
374
+ // Just use the formData value directly for chip display
375
+ selectedOptions.push(value);
363
376
  });
364
377
 
365
378
  return selectedOptions;
366
- }, [formData, options, getOptionValue, getOptionLabel]);
379
+ }, [formData]);
367
380
 
368
381
  var handleInputChange = (0, _react.useCallback)(function (event) {
369
382
  var newValue = event.target.value;
@@ -380,12 +393,11 @@ function MultiTypeaheadField(props) {
380
393
  }, []);
381
394
 
382
395
  var handleOptionClick = (0, _react.useCallback)(function (option) {
383
- var selectedOptions = getSelectedOptions();
384
- var optionValue = getOptionValue(option);
396
+ // Option is already transformed, use it directly
397
+ var optionValue = option;
385
398
 
386
- // Check if already selected
387
- var isSelected = selectedOptions.some(function (selected) {
388
- var selectedValue = getOptionValue(selected);
399
+ // Check if already selected by comparing with formData directly
400
+ var isSelected = formData.some(function (selectedValue) {
389
401
  if ((typeof optionValue === "undefined" ? "undefined" : _typeof(optionValue)) === "object" && (typeof selectedValue === "undefined" ? "undefined" : _typeof(selectedValue)) === "object") {
390
402
  return JSON.stringify(optionValue) === JSON.stringify(selectedValue);
391
403
  }
@@ -400,7 +412,7 @@ function MultiTypeaheadField(props) {
400
412
  // Clear input and close dropdown
401
413
  setInputValue("");
402
414
  setIsOpen(false);
403
- }, [getSelectedOptions, getOptionValue, formData, onChange]);
415
+ }, [formData, onChange]);
404
416
 
405
417
  var handleChipDelete = (0, _react.useCallback)(function (indexToDelete) {
406
418
  var newValues = formData.filter(function (_, index) {
@@ -440,9 +452,9 @@ function MultiTypeaheadField(props) {
440
452
 
441
453
  // Remove already selected options from dropdown
442
454
  filteredOptions = filteredOptions.filter(function (option) {
443
- var optionValue = getOptionValue(option);
444
- return !selectedOptions.some(function (selected) {
445
- var selectedValue = getOptionValue(selected);
455
+ // Option is already transformed, use it directly
456
+ var optionValue = option;
457
+ return !formData.some(function (selectedValue) {
446
458
  if ((typeof optionValue === "undefined" ? "undefined" : _typeof(optionValue)) === "object" && (typeof selectedValue === "undefined" ? "undefined" : _typeof(selectedValue)) === "object") {
447
459
  return JSON.stringify(optionValue) === JSON.stringify(selectedValue);
448
460
  }
@@ -453,6 +465,10 @@ function MultiTypeaheadField(props) {
453
465
  var label = multiTypeaheadConfig.label;
454
466
  var placeholder = multiTypeaheadConfig.placeholder || "Select...";
455
467
 
468
+ // Show dropdown only if input is not empty or there are options
469
+ // Show dropdown only after typing is finished
470
+ var shouldShowDropdown = isOpen && !isTyping && inputValue.trim();
471
+
456
472
  return _react2.default.createElement(
457
473
  _material.ClickAwayListener,
458
474
  { onClickAway: handleClickAway },
@@ -474,9 +490,8 @@ function MultiTypeaheadField(props) {
474
490
  StyledChipContainer,
475
491
  null,
476
492
  selectedOptions.map(function (option, index) {
477
- // Create a stable key based on the option content
478
- var optionValue = getOptionValue(option);
479
- var stableKey = (typeof optionValue === "undefined" ? "undefined" : _typeof(optionValue)) === "object" ? JSON.stringify(optionValue) : String(optionValue);
493
+ // Option is already in transformed format
494
+ var stableKey = (typeof option === "undefined" ? "undefined" : _typeof(option)) === "object" ? JSON.stringify(option) : String(option);
480
495
 
481
496
  return _react2.default.createElement(StyledChip, {
482
497
  key: stableKey,
@@ -511,10 +526,10 @@ function MultiTypeaheadField(props) {
511
526
  )
512
527
  }
513
528
  }),
514
- isOpen && (filteredOptions.length > 0 || loading && inputValue.trim()) ? _react2.default.createElement(
529
+ shouldShowDropdown && _react2.default.createElement(
515
530
  StyledDropdown,
516
531
  null,
517
- loading && inputValue.trim() ? _react2.default.createElement(
532
+ loading ? _react2.default.createElement(
518
533
  StyledLoadingContainer,
519
534
  null,
520
535
  _react2.default.createElement(_material.CircularProgress, { size: 20 }),
@@ -523,7 +538,11 @@ function MultiTypeaheadField(props) {
523
538
  { style: { marginLeft: "8px" } },
524
539
  "Loading..."
525
540
  )
526
- ) : filteredOptions.length > 0 ? filteredOptions.map(function (option, index) {
541
+ ) : filteredOptions.length === 0 ? _react2.default.createElement(
542
+ StyledNoOptionsContainer,
543
+ null,
544
+ "No options available"
545
+ ) : filteredOptions.map(function (option, index) {
527
546
  return _react2.default.createElement(
528
547
  StyledOptionItem,
529
548
  {
@@ -534,12 +553,8 @@ function MultiTypeaheadField(props) {
534
553
  },
535
554
  getOptionLabel(option)
536
555
  );
537
- }) : _react2.default.createElement(
538
- StyledNoOptionsContainer,
539
- null,
540
- "No options available"
541
- )
542
- ) : null
556
+ })
557
+ )
543
558
  )
544
559
  );
545
560
  }
@@ -154,11 +154,11 @@ function mapFromObject(data, mapping, defVal) {
154
154
  return agg;
155
155
  }, defVal);
156
156
  }
157
- /**
158
- *
159
- * @param {*} data
160
- * @param {*} mapping
161
- * Mapped object is converted to the object mapping takes
157
+ /**
158
+ *
159
+ * @param {*} data
160
+ * @param {*} mapping
161
+ * Mapped object is converted to the object mapping takes
162
162
  */
163
163
  function mapFromSchema(data, mapping) {
164
164
  if (isEmpty(data)) {
@@ -223,9 +223,9 @@ function isFunction(functionToCheck) {
223
223
  return functionToCheck instanceof Function;
224
224
  }
225
225
 
226
- /*
227
- this is done to prevent an edge case with a typeahead wrapped inside a table that has an item selected & uses a function as a labelKey
228
- TODO: Need to find a better solution for this
226
+ /*
227
+ this is done to prevent an edge case with a typeahead wrapped inside a table that has an item selected & uses a function as a labelKey
228
+ TODO: Need to find a better solution for this
229
229
  */
230
230
  function transformLabelKey(labelKey, schema, selected) {
231
231
  if (isFunction(labelKey) && selected && selected.length > 0 && schema.type === "string" && selected.every(function (x) {
@@ -1,10 +1,10 @@
1
- /*Collapsible header items*/
2
- .header-elements-wrapper {
3
- align-items: center;
4
- color: #fff;
5
- display: inline-flex;
6
- flex-direction: row;
7
- justify-content: flex-start;
8
- padding-left: 20px;
9
- white-space: nowrap;
1
+ /*Collapsible header items*/
2
+ .header-elements-wrapper {
3
+ align-items: center;
4
+ color: #fff;
5
+ display: inline-flex;
6
+ flex-direction: row;
7
+ justify-content: flex-start;
8
+ padding-left: 20px;
9
+ white-space: nowrap;
10
10
  }