@descope-ui/descope-multi-line-mappings 3.9.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.
@@ -0,0 +1,751 @@
1
+ import { compose } from '@descope-ui/common/utils';
2
+ import {
3
+ getComponentName,
4
+ injectStyle,
5
+ } from '@descope-ui/common/components-helpers';
6
+ import {
7
+ createStyleMixin,
8
+ draggableMixin,
9
+ } from '@descope-ui/common/components-mixins';
10
+ import { createBaseClass } from '@descope-ui/common/base-classes';
11
+ import { MultiSelectComboBoxClass } from '@descope-ui/descope-multi-select-combo-box/class';
12
+ import { ButtonClass } from '@descope-ui/descope-button/class';
13
+
14
+ export const componentName = getComponentName('multi-line-mappings');
15
+ const BaseClass = createBaseClass({ componentName });
16
+
17
+ const parseJSON = (value, fallback, source = 'unknown') => {
18
+ if (typeof value !== 'string' || !value?.trim()) {
19
+ return fallback;
20
+ }
21
+
22
+ try {
23
+ return JSON.parse(value);
24
+ } catch (error) {
25
+ console.error(
26
+ `[descope-multi-line-mappings] Failed to parse JSON from "${source}"`,
27
+ error,
28
+ );
29
+ return fallback;
30
+ }
31
+ };
32
+
33
+ const toNormalizedValue = (item) => {
34
+ if (item === undefined || item === null) {
35
+ return '';
36
+ }
37
+
38
+ if (typeof item === 'object') {
39
+ return String(item.value ?? item.id ?? item.label ?? '');
40
+ }
41
+
42
+ return String(item);
43
+ };
44
+
45
+ const uniqueValues = (values) => [
46
+ ...new Set(values.map(toNormalizedValue).filter(Boolean)),
47
+ ];
48
+
49
+ const normalizeOption = (item) => {
50
+ const value = toNormalizedValue(item);
51
+
52
+ if (typeof item === 'object' && item !== null) {
53
+ return {
54
+ ...item,
55
+ value,
56
+ label: String(item.label ?? item.displayName ?? value),
57
+ };
58
+ }
59
+
60
+ return { value, label: value };
61
+ };
62
+
63
+ const normalizeOptions = (options) => {
64
+ if (!Array.isArray(options)) {
65
+ return [];
66
+ }
67
+
68
+ const uniqueSet = new Set();
69
+
70
+ return options.map(normalizeOption).filter((option) => {
71
+ if (!option.value || uniqueSet.has(option.value)) {
72
+ return false;
73
+ }
74
+
75
+ uniqueSet.add(option.value);
76
+ return true;
77
+ });
78
+ };
79
+
80
+ const createEmptyRow = () => ({ firstValues: [], secondValues: [] });
81
+
82
+ const areSameValues = (first = [], second = []) =>
83
+ first.length === second.length && first.every((v, i) => v === second[i]);
84
+
85
+ class RawMultiLineMappingsClass extends BaseClass {
86
+ static get observedAttributes() {
87
+ return [
88
+ 'data',
89
+ 'max-rows',
90
+ 'size',
91
+ 'disabled',
92
+ 'readonly',
93
+ 'first-value-key',
94
+ 'second-value-key',
95
+ 'first-label',
96
+ 'second-label',
97
+ 'first-placeholder',
98
+ 'second-placeholder',
99
+ 'full-width',
100
+ 'merge-rows',
101
+ ];
102
+ }
103
+
104
+ #rows = [createEmptyRow()];
105
+
106
+ #rawValue = null;
107
+
108
+ #firstOptions = [];
109
+
110
+ #secondOptionsByFirstValue = {};
111
+
112
+ constructor() {
113
+ super();
114
+
115
+ this.attachShadow({ mode: 'open' }).innerHTML = `
116
+ <div class="wrapper">
117
+ <div class="rows"></div>
118
+ <div class="add-action">
119
+ <descope-button
120
+ class="add-button"
121
+ variant="link"
122
+ mode="primary"
123
+ >
124
+ <slot name="add-button-content">Add line</slot>
125
+ </descope-button>
126
+ </div>
127
+ </div>
128
+ `;
129
+
130
+ injectStyle(
131
+ `
132
+ :host {
133
+ display: inline-flex;
134
+ box-sizing: border-box;
135
+ max-width: 100%;
136
+ }
137
+
138
+ .wrapper {
139
+ display: flex;
140
+ flex-direction: column;
141
+ width: 100%;
142
+ }
143
+
144
+ .rows {
145
+ display: flex;
146
+ flex-direction: column;
147
+ width: 100%;
148
+ }
149
+
150
+ .rows > .row {
151
+ display: flex;
152
+ width: 100%;
153
+ min-width: 0;
154
+ box-sizing: border-box;
155
+ align-items: flex-end;
156
+ }
157
+
158
+ .row descope-multi-select-combo-box {
159
+ flex: 1 1 0;
160
+ box-sizing: border-box;
161
+ }
162
+
163
+ .row .remove-button {
164
+ flex: 0 0 auto;
165
+ align-self: flex-end;
166
+ }
167
+
168
+ .row:only-child .remove-button {
169
+ display: none;
170
+ pointer-events: none;
171
+ }
172
+
173
+ .add-action {
174
+ display: flex;
175
+ }
176
+ `,
177
+ this,
178
+ );
179
+
180
+ this.rowsElement = this.shadowRoot.querySelector('.rows');
181
+ this.addButtonElement = this.shadowRoot.querySelector('.add-button');
182
+ }
183
+
184
+ get size() {
185
+ return this.getAttribute('size') || 'md';
186
+ }
187
+
188
+ get firstValueKey() {
189
+ return this.getAttribute('first-value-key') || 'firstValues';
190
+ }
191
+
192
+ get secondValueKey() {
193
+ return this.getAttribute('second-value-key') || 'secondValues';
194
+ }
195
+
196
+ get firstLabel() {
197
+ return this.getAttribute('first-label') || '';
198
+ }
199
+
200
+ get secondLabel() {
201
+ return this.getAttribute('second-label') || '';
202
+ }
203
+
204
+ get firstPlaceholder() {
205
+ return this.getAttribute('first-placeholder') || '';
206
+ }
207
+
208
+ get secondPlaceholder() {
209
+ return this.getAttribute('second-placeholder') || '';
210
+ }
211
+
212
+ get disabled() {
213
+ return this.getAttribute('disabled') === 'true';
214
+ }
215
+
216
+ get readonly() {
217
+ return this.getAttribute('readonly') === 'true';
218
+ }
219
+
220
+ get mergeRows() {
221
+ return this.getAttribute('merge-rows') === 'true';
222
+ }
223
+
224
+ get maxRows() {
225
+ const maxRows = Number(this.getAttribute('max-rows'));
226
+
227
+ return Number.isFinite(maxRows) && maxRows > 0 ? maxRows : undefined;
228
+ }
229
+
230
+ get data() {
231
+ return this.#secondOptionsByFirstValue;
232
+ }
233
+
234
+ get value() {
235
+ return this.#rows
236
+ .filter(
237
+ (row) => row.firstValues.length > 0 || row.secondValues.length > 0,
238
+ )
239
+ .flatMap((rowValue) => {
240
+ const firsts = rowValue.firstValues.length
241
+ ? rowValue.firstValues
242
+ : [''];
243
+ return firsts.map((firstValue) => ({
244
+ [this.firstValueKey]: firstValue,
245
+ [this.secondValueKey]: [...rowValue.secondValues],
246
+ }));
247
+ });
248
+ }
249
+
250
+ set value(value) {
251
+ this.#rawValue = value;
252
+ this.#rows = this.#normalizeRows(value);
253
+ this.#dropStaleSelections();
254
+ this.#renderRows();
255
+ }
256
+
257
+ #setData(rawValue) {
258
+ const parsed = parseJSON(rawValue, {}, 'data attribute');
259
+ const optionsMap =
260
+ parsed && typeof parsed === 'object' && !Array.isArray(parsed)
261
+ ? parsed
262
+ : {};
263
+
264
+ this.#firstOptions = Object.entries(optionsMap).map(([key, value]) => {
265
+ const strKey = String(key);
266
+ const isEnriched =
267
+ value != null && typeof value === 'object' && !Array.isArray(value);
268
+ const options = isEnriched ? (value.options ?? []) : value;
269
+ const label =
270
+ isEnriched && value.label != null ? String(value.label) : strKey;
271
+ return { value: strKey, label, options: normalizeOptions(options) };
272
+ });
273
+ this.#secondOptionsByFirstValue = Object.fromEntries(
274
+ this.#firstOptions.map(({ value, options }) => [value, options]),
275
+ );
276
+ }
277
+
278
+ init() {
279
+ super.init?.();
280
+ this.#updateAddButtonSize();
281
+ this.addButtonElement.addEventListener('click', () => this.#addRow());
282
+ this.#renderRows();
283
+ }
284
+
285
+ attributeChangedCallback(name, oldValue, newValue) {
286
+ super.attributeChangedCallback?.(name, oldValue, newValue);
287
+ if (oldValue === newValue) return;
288
+
289
+ if (name === 'data') {
290
+ this.#setData(newValue);
291
+ this.#dropStaleSelections();
292
+ this.#renderRows();
293
+ return;
294
+ }
295
+
296
+ if (name === 'size') {
297
+ [...this.#getAllCombos(), ...this.#getAllRemoveButtons()].forEach((el) =>
298
+ el.setAttribute('size', this.size),
299
+ );
300
+ this.#updateAddButtonSize();
301
+ this.#updateAddButtonState();
302
+ return;
303
+ }
304
+
305
+ if (['disabled', 'readonly'].includes(name)) {
306
+ this.#getAllCombos().forEach((combo) => {
307
+ combo[this.disabled ? 'setAttribute' : 'removeAttribute'](
308
+ 'disabled',
309
+ 'true',
310
+ );
311
+ combo[this.readonly ? 'setAttribute' : 'removeAttribute'](
312
+ 'readonly',
313
+ 'true',
314
+ );
315
+ });
316
+ this.#getAllRemoveButtons().forEach((el) =>
317
+ el[this.disabled || this.readonly ? 'setAttribute' : 'removeAttribute'](
318
+ 'disabled',
319
+ 'true',
320
+ ),
321
+ );
322
+ this.#updateAddButtonState();
323
+ return;
324
+ }
325
+
326
+ if (['first-label', 'second-label'].includes(name)) {
327
+ const type = name === 'first-label' ? 'first' : 'second';
328
+ Array.from(this.rowsElement.children).forEach((rowElement) => {
329
+ const combo = rowElement.querySelector(`.${type}-combo`);
330
+ if (newValue) combo?.setAttribute('label', newValue);
331
+ else combo?.removeAttribute('label');
332
+ });
333
+ return;
334
+ }
335
+
336
+ if (['first-placeholder', 'second-placeholder'].includes(name)) {
337
+ const type = name === 'first-placeholder' ? 'first' : 'second';
338
+ Array.from(this.rowsElement.children).forEach((rowElement) => {
339
+ const combo = rowElement.querySelector(`.${type}-combo`);
340
+ if (newValue) combo?.setAttribute('placeholder', newValue);
341
+ else combo?.removeAttribute('placeholder');
342
+ });
343
+ return;
344
+ }
345
+
346
+ if (name === 'max-rows') {
347
+ this.#updateAddButtonState();
348
+ }
349
+
350
+ if (name === 'merge-rows' && this.#rawValue !== null) {
351
+ this.#rows = this.#normalizeRows(this.#rawValue);
352
+ this.#dropStaleSelections();
353
+ this.#renderRows();
354
+ }
355
+ }
356
+
357
+ #getAllCombos() {
358
+ return Array.from(
359
+ this.rowsElement.querySelectorAll('descope-multi-select-combo-box'),
360
+ );
361
+ }
362
+
363
+ #getAllRemoveButtons() {
364
+ return Array.from(this.rowsElement.querySelectorAll('.remove-button'));
365
+ }
366
+
367
+ #addRow() {
368
+ if (this.disabled || this.readonly || this.#isMaxRowsReached()) {
369
+ return;
370
+ }
371
+
372
+ const newRow = createEmptyRow();
373
+ this.#rows = [...this.#rows, newRow];
374
+ this.rowsElement.append(this.#createRowElement(newRow));
375
+ this.#updateAddButtonState();
376
+ }
377
+
378
+ #normalizeRows(value) {
379
+ if (!Array.isArray(value) || !value.length) {
380
+ return [createEmptyRow()];
381
+ }
382
+
383
+ const rows = value.map((rowValue) => {
384
+ const firstRaw = rowValue?.[this.firstValueKey];
385
+ const firstValues =
386
+ firstRaw != null && String(firstRaw) !== '' ? [String(firstRaw)] : [];
387
+
388
+ return {
389
+ firstValues,
390
+ secondValues: uniqueValues(rowValue?.[this.secondValueKey] ?? []),
391
+ };
392
+ });
393
+
394
+ let result = rows;
395
+
396
+ if (this.mergeRows) {
397
+ const map = new Map();
398
+ for (const row of result) {
399
+ const key = [...row.secondValues].sort().join('\0');
400
+ if (map.has(key)) {
401
+ map.get(key).firstValues.push(...row.firstValues);
402
+ } else {
403
+ map.set(key, {
404
+ firstValues: [...row.firstValues],
405
+ secondValues: row.secondValues,
406
+ });
407
+ }
408
+ }
409
+ result = [...map.values()];
410
+ }
411
+
412
+ return result;
413
+ }
414
+
415
+ #dropStaleSelections() {
416
+ const firstValuesSet = new Set(this.#firstOptions.map((o) => o.value));
417
+
418
+ this.#rows = this.#rows.map((rowValue) => {
419
+ const firstValues = rowValue.firstValues.filter((value) =>
420
+ firstValuesSet.has(value),
421
+ );
422
+ const availableSecondOptions =
423
+ this.#getSecondOptionsForFirstValues(firstValues);
424
+ const availableSecondValuesSet = new Set(
425
+ availableSecondOptions.map((item) => item.value),
426
+ );
427
+
428
+ const secondValues = rowValue.secondValues.filter((value) =>
429
+ availableSecondValuesSet.has(value),
430
+ );
431
+
432
+ return {
433
+ firstValues,
434
+ secondValues,
435
+ };
436
+ });
437
+ }
438
+
439
+ #getSecondOptionsForFirstValues(firstValues) {
440
+ // Get lists to intersect
441
+ const lists = !firstValues.length
442
+ ? Object.values(this.#secondOptionsByFirstValue).filter(
443
+ (options) => options.length,
444
+ )
445
+ : firstValues
446
+ .map((value) => this.#secondOptionsByFirstValue[value] || [])
447
+ .filter((options) => options.length);
448
+
449
+ if (
450
+ !lists.length ||
451
+ (firstValues.length && lists.length !== firstValues.length)
452
+ ) {
453
+ return [];
454
+ }
455
+
456
+ if (lists.length === 1) {
457
+ return lists[0];
458
+ }
459
+
460
+ // Compute intersection of all lists
461
+ const [baseList, ...otherLists] = lists;
462
+ const intersectionSet = new Set(baseList.map((item) => item.value));
463
+
464
+ for (const options of otherLists) {
465
+ const currentSet = new Set(options.map((item) => item.value));
466
+ for (const value of intersectionSet) {
467
+ if (!currentSet.has(value)) {
468
+ intersectionSet.delete(value);
469
+ }
470
+ }
471
+ }
472
+
473
+ return baseList.filter((item) => intersectionSet.has(item.value));
474
+ }
475
+
476
+ #createCombo({
477
+ getRowIndex,
478
+ type,
479
+ label,
480
+ placeholder,
481
+ options,
482
+ selectedValues,
483
+ }) {
484
+ const comboElement = document.createElement(
485
+ 'descope-multi-select-combo-box',
486
+ );
487
+
488
+ comboElement.className = `combo ${type}-combo`;
489
+ comboElement.setAttribute('size', this.size);
490
+ comboElement.setAttribute('bordered', 'true');
491
+ comboElement.setAttribute('item-label-path', 'data-name');
492
+ comboElement.setAttribute('item-value-path', 'data-id');
493
+
494
+ if (label) {
495
+ comboElement.setAttribute('label', label);
496
+ }
497
+
498
+ if (placeholder) {
499
+ comboElement.setAttribute('placeholder', placeholder);
500
+ }
501
+
502
+ if (this.disabled) {
503
+ comboElement.setAttribute('disabled', 'true');
504
+ }
505
+
506
+ if (this.readonly) {
507
+ comboElement.setAttribute('readonly', 'true');
508
+ }
509
+
510
+ comboElement.data = options;
511
+ this.#syncComboSelection(comboElement, selectedValues);
512
+
513
+ comboElement.addEventListener('input', () => {
514
+ if (comboElement.__descopeSyncingSelection) {
515
+ return;
516
+ }
517
+
518
+ const rowIndex = getRowIndex();
519
+ if (rowIndex === -1) return;
520
+
521
+ this.#handleComboChange({
522
+ rowIndex,
523
+ type,
524
+ selectedValues: uniqueValues(comboElement.value || []),
525
+ });
526
+ });
527
+
528
+ return comboElement;
529
+ }
530
+
531
+ #syncComboSelection(comboElement, selectedValues) {
532
+ comboElement.__descopeSyncingSelection = true;
533
+ Promise.resolve().then(() => {
534
+ const selectedValuesSet = new Set(selectedValues || []);
535
+ comboElement.selectedItems = (comboElement.items || []).filter((item) =>
536
+ selectedValuesSet.has(
537
+ item['data-id'] || item.getAttribute?.('data-id'),
538
+ ),
539
+ );
540
+ comboElement.__descopeSyncingSelection = false;
541
+ });
542
+ }
543
+
544
+ #createRemoveButton(getRowIndex) {
545
+ const buttonElement = document.createElement('descope-button');
546
+
547
+ buttonElement.className = 'remove-button';
548
+ buttonElement.setAttribute('variant', 'link');
549
+ buttonElement.setAttribute('mode', 'primary');
550
+ buttonElement.setAttribute('size', this.size);
551
+ buttonElement.setAttribute('aria-label', 'Remove row');
552
+ buttonElement.innerHTML = '<vaadin-icon icon="vaadin:minus"></vaadin-icon>';
553
+
554
+ if (this.disabled || this.readonly) {
555
+ buttonElement.setAttribute('disabled', 'true');
556
+ }
557
+
558
+ buttonElement.addEventListener('click', () => {
559
+ const rowIndex = getRowIndex();
560
+ if (rowIndex !== -1) this.#removeRow(rowIndex);
561
+ });
562
+
563
+ return buttonElement;
564
+ }
565
+
566
+ #renderRows() {
567
+ this.rowsElement.innerHTML = '';
568
+ this.#rows.forEach((rowValue) => {
569
+ this.rowsElement.append(this.#createRowElement(rowValue));
570
+ });
571
+ this.#updateAddButtonState();
572
+ }
573
+
574
+ #createRowElement(rowValue) {
575
+ const rowElement = document.createElement('div');
576
+ rowElement.className = 'row';
577
+
578
+ const getRowIndex = () =>
579
+ Array.from(this.rowsElement.children).indexOf(rowElement);
580
+
581
+ const firstCombo = this.#createCombo({
582
+ getRowIndex,
583
+ type: 'first',
584
+ label: this.firstLabel,
585
+ placeholder: this.firstPlaceholder,
586
+ options: this.#firstOptions.map(({ value, label }) => ({ value, label })),
587
+ selectedValues: rowValue.firstValues,
588
+ });
589
+
590
+ const secondOptions = this.#getSecondOptionsForFirstValues(
591
+ rowValue.firstValues,
592
+ );
593
+ const secondCombo = this.#createCombo({
594
+ getRowIndex,
595
+ type: 'second',
596
+ label: this.secondLabel,
597
+ placeholder: this.secondPlaceholder,
598
+ options: secondOptions,
599
+ selectedValues: rowValue.secondValues,
600
+ });
601
+
602
+ rowElement.append(
603
+ firstCombo,
604
+ secondCombo,
605
+ this.#createRemoveButton(getRowIndex),
606
+ );
607
+ return rowElement;
608
+ }
609
+
610
+ #updateSecondComboForRow(rowIndex) {
611
+ const currentRowValue = this.#rows[rowIndex];
612
+ if (!currentRowValue) return;
613
+
614
+ const rowElement = this.rowsElement.children[rowIndex];
615
+ const secondCombo = rowElement?.querySelector('.second-combo');
616
+ if (!secondCombo) {
617
+ this.#renderRows();
618
+ return;
619
+ }
620
+
621
+ const secondOptions = this.#getSecondOptionsForFirstValues(
622
+ currentRowValue.firstValues,
623
+ );
624
+ secondCombo.data = secondOptions;
625
+ this.#syncComboSelection(secondCombo, currentRowValue.secondValues);
626
+ }
627
+
628
+ #updateAddButtonSize() {
629
+ this.addButtonElement.setAttribute('size', this.size);
630
+ }
631
+
632
+ #updateAddButtonState() {
633
+ if (this.disabled || this.readonly || this.#isMaxRowsReached()) {
634
+ this.addButtonElement.setAttribute('disabled', 'true');
635
+ } else {
636
+ this.addButtonElement.removeAttribute('disabled');
637
+ }
638
+ }
639
+
640
+ #isMaxRowsReached() {
641
+ return Number.isFinite(this.maxRows) && this.#rows.length >= this.maxRows;
642
+ }
643
+
644
+ #removeRowFromDOM(rowIndex) {
645
+ this.rowsElement.children[rowIndex]?.remove();
646
+ }
647
+
648
+ #handleComboChange({ rowIndex, type, selectedValues }) {
649
+ if (this.disabled || this.readonly) {
650
+ return;
651
+ }
652
+
653
+ const currentRow = this.#rows[rowIndex];
654
+
655
+ if (!currentRow) {
656
+ return;
657
+ }
658
+
659
+ if (type === 'first') {
660
+ const availableSecondOptions =
661
+ this.#getSecondOptionsForFirstValues(selectedValues);
662
+ const availableSecondValuesSet = new Set(
663
+ availableSecondOptions.map((item) => item.value),
664
+ );
665
+
666
+ const nextSecondValues = currentRow.secondValues.filter((value) =>
667
+ availableSecondValuesSet.has(value),
668
+ );
669
+
670
+ if (
671
+ areSameValues(currentRow.firstValues, selectedValues) &&
672
+ areSameValues(currentRow.secondValues, nextSecondValues)
673
+ ) {
674
+ return;
675
+ }
676
+
677
+ this.#rows[rowIndex] = {
678
+ firstValues: selectedValues,
679
+ secondValues: nextSecondValues,
680
+ };
681
+
682
+ this.#updateSecondComboForRow(rowIndex);
683
+ this.#emitValueChange();
684
+ return;
685
+ }
686
+
687
+ if (areSameValues(currentRow.secondValues, selectedValues)) {
688
+ return;
689
+ }
690
+
691
+ this.#rows[rowIndex] = {
692
+ ...currentRow,
693
+ secondValues: selectedValues,
694
+ };
695
+
696
+ this.#emitValueChange();
697
+ }
698
+
699
+ #removeRow(rowIndex) {
700
+ if (this.disabled || this.readonly || this.#rows.length <= 1) {
701
+ return;
702
+ }
703
+
704
+ const removedRow = this.#rows[rowIndex];
705
+ this.#rows = this.#rows.filter((_, index) => index !== rowIndex);
706
+
707
+ this.#removeRowFromDOM(rowIndex);
708
+ this.#updateAddButtonState();
709
+
710
+ const rowHadValues =
711
+ removedRow.firstValues.length > 0 || removedRow.secondValues.length > 0;
712
+ if (rowHadValues) {
713
+ this.#emitValueChange();
714
+ }
715
+ }
716
+
717
+ #emitValueChange() {
718
+ this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
719
+ }
720
+ }
721
+
722
+ const host = { selector: () => ':host' };
723
+ const rows = { selector: '.rows' };
724
+ const row = { selector: '.row' };
725
+ const addAction = { selector: '.add-action' };
726
+
727
+ export const MultiLineMappingsClass = compose(
728
+ createStyleMixin({
729
+ mappings: {
730
+ hostWidth: { ...host, property: 'width' },
731
+ hostDirection: [
732
+ { ...host, property: 'direction' },
733
+ {
734
+ selector: MultiSelectComboBoxClass.componentName,
735
+ property: MultiSelectComboBoxClass.cssVarList.hostDirection,
736
+ },
737
+ {
738
+ selector: ButtonClass.componentName,
739
+ property: ButtonClass.cssVarList.hostDirection,
740
+ },
741
+ ],
742
+ rowsGap: { ...rows, property: 'gap' },
743
+ rowGap: { ...row, property: 'gap' },
744
+ rowAlignItems: { ...row, property: 'align-items' },
745
+ rowWrap: { ...row, property: 'flex-wrap' },
746
+ addActionJustifyContent: { ...addAction, property: 'justify-content' },
747
+ addActionMarginTop: { ...addAction, property: 'margin-top' },
748
+ },
749
+ }),
750
+ draggableMixin,
751
+ )(RawMultiLineMappingsClass);