superdesk-ui-framework 3.0.43 → 3.0.45
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/app-typescript/components/Menu.tsx +1 -1
- package/app-typescript/components/{TreeSelect.tsx → TreeSelect/TreeSelect.tsx} +228 -257
- package/app-typescript/components/TreeSelect/TreeSelectItem.tsx +84 -0
- package/app-typescript/components/TreeSelect/TreeSelectPill.tsx +53 -0
- package/app-typescript/index.ts +1 -1
- package/dist/examples.bundle.js +1026 -913
- package/dist/react/TreeSelect.tsx +1 -1
- package/dist/superdesk-ui.bundle.js +773 -660
- package/dist/vendor.bundle.js +23 -23
- package/examples/pages/react/TreeSelect.tsx +1 -1
- package/package.json +1 -1
- package/react/components/Menu.js +1 -1
- package/react/components/{TreeSelect.d.ts → TreeSelect/TreeSelect.d.ts} +2 -2
- package/react/components/{TreeSelect.js → TreeSelect/TreeSelect.js} +82 -144
- package/react/components/TreeSelect/TreeSelectItem.d.ts +20 -0
- package/react/components/TreeSelect/TreeSelectItem.js +90 -0
- package/react/components/TreeSelect/TreeSelectPill.d.ts +14 -0
- package/react/components/TreeSelect/TreeSelectPill.js +71 -0
- package/react/index.d.ts +1 -1
- package/react/index.js +1 -1
@@ -1,14 +1,16 @@
|
|
1
1
|
import * as React from "react";
|
2
|
-
import { Icon } from "
|
3
|
-
import { Loader } from "
|
2
|
+
import { Icon } from "../Icon";
|
3
|
+
import { Loader } from "../Loader";
|
4
4
|
import nextId from "react-id-generator";
|
5
5
|
import _debounce from 'lodash/debounce';
|
6
|
-
import { InputWrapper } from "
|
6
|
+
import { InputWrapper } from "../Form";
|
7
7
|
import { createPopper, Instance } from '@popperjs/core';
|
8
8
|
import {isEqual} from 'lodash';
|
9
|
-
import {getTextColor} from '
|
10
|
-
import {IInputWrapper} from '
|
11
|
-
import {SelectPreview} from '
|
9
|
+
import {getTextColor} from '../Label';
|
10
|
+
import {IInputWrapper} from '../Form/InputWrapper';
|
11
|
+
import {SelectPreview} from '../SelectPreview';
|
12
|
+
import {TreeSelectPill} from './TreeSelectPill';
|
13
|
+
import {getPrefixedItemId, TreeSelectItem} from './TreeSelectItem';
|
12
14
|
|
13
15
|
interface IState<T> {
|
14
16
|
value: Array<T>;
|
@@ -22,7 +24,9 @@ interface IState<T> {
|
|
22
24
|
buttonValue: ITreeNode<T> | null;
|
23
25
|
buttonMouseEvent: boolean;
|
24
26
|
loading: boolean;
|
25
|
-
|
27
|
+
|
28
|
+
// array of classNames; used to focus an item after returning for another level
|
29
|
+
buttonTarget: Array<string>;
|
26
30
|
}
|
27
31
|
|
28
32
|
interface IPropsBase<T> extends IInputWrapper {
|
@@ -71,6 +75,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
71
75
|
private inputRef: React.RefObject<HTMLInputElement>;
|
72
76
|
private categoryButtonRef: React.RefObject<HTMLButtonElement>;
|
73
77
|
private openDropdownRef: React.RefObject<HTMLButtonElement>;
|
78
|
+
private treeSelectRef: React.RefObject<HTMLDivElement>;
|
74
79
|
private htmlId: string = nextId();
|
75
80
|
private popperInstance: Instance | null;
|
76
81
|
|
@@ -80,22 +85,21 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
80
85
|
value: this.props.value ? this.props.value : [],
|
81
86
|
options: this.props.getOptions ? this.props.getOptions() : [],
|
82
87
|
firstBranchOptions: this.props.getOptions ? this.props.getOptions() : [],
|
88
|
+
searchFieldValue: '',
|
83
89
|
activeTree: [],
|
84
90
|
filterArr: [],
|
85
|
-
searchFieldValue: '',
|
86
91
|
buttonTree: [],
|
92
|
+
buttonTarget: [],
|
87
93
|
buttonValue: null,
|
88
94
|
buttonMouseEvent: false,
|
89
95
|
openDropdown: false,
|
90
96
|
loading: false,
|
91
|
-
buttonTarget: [],
|
92
97
|
};
|
93
98
|
|
94
99
|
this.removeClick = this.removeClick.bind(this);
|
95
100
|
this.handleMultiLevel = this.handleMultiLevel.bind(this);
|
96
101
|
this.backButton = this.backButton.bind(this);
|
97
102
|
this.handleButton = this.handleButton.bind(this);
|
98
|
-
this.backButtonValue = this.backButtonValue.bind(this);
|
99
103
|
this.handleTree = this.handleTree.bind(this);
|
100
104
|
this.filteredItem = this.filteredItem.bind(this);
|
101
105
|
this.branchButton = this.branchButton.bind(this);
|
@@ -108,24 +112,27 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
108
112
|
this.inputRef = React.createRef();
|
109
113
|
this.categoryButtonRef = React.createRef();
|
110
114
|
this.openDropdownRef = React.createRef();
|
115
|
+
this.treeSelectRef = React.createRef();
|
111
116
|
this.popperInstance = null;
|
112
117
|
}
|
113
118
|
|
114
119
|
inputFocus = () => {
|
115
120
|
this.inputRef.current?.focus();
|
116
121
|
}
|
122
|
+
|
117
123
|
listNavigation = () => {
|
118
124
|
const element: HTMLElement = document.querySelector('.suggestion-item--btn') as HTMLElement;
|
119
125
|
element.focus();
|
120
126
|
}
|
127
|
+
|
121
128
|
buttonFocus = () => {
|
122
129
|
this.categoryButtonRef.current?.focus();
|
123
130
|
}
|
124
131
|
|
125
132
|
onMouseDown = (event: MouseEvent) => {
|
126
133
|
if (
|
127
|
-
(this.
|
128
|
-
&&
|
134
|
+
(this.treeSelectRef .current?.contains(event.target as HTMLElement) !== true)
|
135
|
+
&& this.state.openDropdown
|
129
136
|
) {
|
130
137
|
this.setState({openDropdown: false});
|
131
138
|
}
|
@@ -138,14 +145,14 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
138
145
|
this.ref.current,
|
139
146
|
this.categoryButtonRef.current ? this.buttonFocus : this.inputFocus,
|
140
147
|
);
|
141
|
-
|
148
|
+
|
149
|
+
if (e.key === 'Backspace' && this.state.activeTree.length > 0) {
|
142
150
|
this.backButton();
|
143
|
-
this.backButtonValue();
|
144
151
|
|
145
|
-
const
|
146
|
-
const className = `${buttonTarget.pop()}-focus`;
|
152
|
+
const lastElement = this.state.buttonTarget.pop();
|
147
153
|
|
148
|
-
if (
|
154
|
+
if (lastElement != null) {
|
155
|
+
const className = getPrefixedItemId(lastElement);
|
149
156
|
const element: HTMLElement = document.getElementsByClassName(className)[0] as HTMLElement;
|
150
157
|
element.focus();
|
151
158
|
}
|
@@ -171,13 +178,17 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
171
178
|
} else if (!isEqual(prevProps.value, this.props.value)) {
|
172
179
|
this.props.onChange(this.state.value);
|
173
180
|
}
|
181
|
+
|
174
182
|
if (prevState.openDropdown !== this.state.openDropdown) {
|
175
183
|
this.toggleMenu();
|
176
184
|
}
|
185
|
+
|
177
186
|
if (this.props.kind === 'synchronous') {
|
178
|
-
if (
|
179
|
-
|
180
|
-
|
187
|
+
if (
|
188
|
+
(prevState.activeTree !== this.state.activeTree)
|
189
|
+
|| (prevState.filterArr !== this.state.filterArr)
|
190
|
+
|| (prevState.options !== this.state.options)
|
191
|
+
) {
|
181
192
|
this.popperInstance?.update();
|
182
193
|
}
|
183
194
|
}
|
@@ -198,6 +209,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
198
209
|
],
|
199
210
|
});
|
200
211
|
}
|
212
|
+
|
201
213
|
this.inputRef.current?.addEventListener('keydown', (e: KeyboardEvent) => {
|
202
214
|
if (e.key === 'ArrowDown') {
|
203
215
|
e.preventDefault();
|
@@ -212,6 +224,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
212
224
|
}
|
213
225
|
}
|
214
226
|
});
|
227
|
+
|
215
228
|
if (this.inputRef.current) {
|
216
229
|
this.inputFocus();
|
217
230
|
} else {
|
@@ -230,6 +243,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
230
243
|
this.setState({
|
231
244
|
value: newTags,
|
232
245
|
});
|
246
|
+
|
233
247
|
this.props.onChange(this.state.value);
|
234
248
|
}
|
235
249
|
|
@@ -260,9 +274,11 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
260
274
|
let checkItem = this.state.value.find((valueItem: T) => {
|
261
275
|
return this.props.getId(valueItem) === this.props.getId(item.value);
|
262
276
|
});
|
277
|
+
|
263
278
|
if (!checkItem) {
|
264
279
|
this.setState({value: [...this.state.value, item.value]});
|
265
280
|
}
|
281
|
+
|
266
282
|
if (!event.ctrlKey) {
|
267
283
|
if (this.props.getOptions) {
|
268
284
|
this.setState({
|
@@ -275,14 +291,17 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
275
291
|
this.setState({activeTree: [], buttonTarget: [], openDropdown: false});
|
276
292
|
}
|
277
293
|
}
|
294
|
+
|
278
295
|
this.setState({buttonMouseEvent: false});
|
279
296
|
} else {
|
280
297
|
let checkItem = this.state.value.find((valueItem: T) => {
|
281
298
|
return this.props.getId(valueItem) === this.props.getId(item.value);
|
282
299
|
});
|
300
|
+
|
283
301
|
if (!checkItem) {
|
284
302
|
this.setState({value: [item.value]});
|
285
303
|
}
|
304
|
+
|
286
305
|
if (!event.ctrlKey) {
|
287
306
|
this.setState({
|
288
307
|
options: this.state.firstBranchOptions,
|
@@ -292,47 +311,50 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
292
311
|
openDropdown: false,
|
293
312
|
});
|
294
313
|
}
|
314
|
+
|
295
315
|
this.setState({buttonMouseEvent: false});
|
296
316
|
}
|
297
317
|
}
|
298
318
|
|
299
319
|
handleBranchValue(event: React.MouseEvent<HTMLButtonElement, MouseEvent>, item: ITreeNode<T>) {
|
300
320
|
if (this.props.allowMultiple) {
|
301
|
-
|
302
|
-
|
303
|
-
|
321
|
+
let checkItem = this.state.value.find((valueItem: T) => {
|
322
|
+
return this.props.getId(valueItem) === this.props.getId(item.value);
|
323
|
+
});
|
324
|
+
|
325
|
+
if (!checkItem) {
|
326
|
+
this.setState({value: [...this.state.value, item.value]});
|
327
|
+
}
|
328
|
+
|
329
|
+
if (!event.ctrlKey) {
|
330
|
+
this.setState({
|
331
|
+
options: this.state.firstBranchOptions,
|
332
|
+
activeTree: [],
|
333
|
+
buttonTarget: [],
|
334
|
+
openDropdown: false,
|
304
335
|
});
|
305
|
-
if (!checkItem) {
|
306
|
-
this.setState({value: [...this.state.value, item.value]});
|
307
|
-
}
|
308
|
-
if (!event.ctrlKey) {
|
309
|
-
this.setState({
|
310
|
-
options: this.state.firstBranchOptions,
|
311
|
-
activeTree: [],
|
312
|
-
buttonTarget: [],
|
313
|
-
openDropdown: false,
|
314
|
-
});
|
315
|
-
}
|
316
|
-
this.setState({buttonMouseEvent: false});
|
317
336
|
}
|
337
|
+
|
338
|
+
this.setState({buttonMouseEvent: false});
|
318
339
|
} else {
|
319
|
-
|
320
|
-
|
321
|
-
|
340
|
+
let checkItem = this.state.value.find((valueItem: T) => {
|
341
|
+
return this.props.getId(valueItem) === this.props.getId(item.value);
|
342
|
+
});
|
343
|
+
|
344
|
+
if (!checkItem) {
|
345
|
+
this.setState({value: [item.value]});
|
346
|
+
}
|
347
|
+
|
348
|
+
if (!event.ctrlKey) {
|
349
|
+
this.setState({
|
350
|
+
options: this.state.firstBranchOptions,
|
351
|
+
activeTree: [],
|
352
|
+
buttonTarget: [],
|
353
|
+
openDropdown: false,
|
322
354
|
});
|
323
|
-
if (!checkItem) {
|
324
|
-
this.setState({value: [item.value]});
|
325
|
-
}
|
326
|
-
if (!event.ctrlKey) {
|
327
|
-
this.setState({
|
328
|
-
options: this.state.firstBranchOptions,
|
329
|
-
activeTree: [],
|
330
|
-
buttonTarget: [],
|
331
|
-
openDropdown: false,
|
332
|
-
});
|
333
|
-
}
|
334
|
-
this.setState({buttonMouseEvent: false});
|
335
355
|
}
|
356
|
+
|
357
|
+
this.setState({buttonMouseEvent: false});
|
336
358
|
}
|
337
359
|
}
|
338
360
|
|
@@ -340,14 +362,17 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
340
362
|
if (option.children) {
|
341
363
|
this.handleButton(option);
|
342
364
|
this.handleMultiLevel(option);
|
365
|
+
|
343
366
|
if (event.altKey && this.props.allowMultiple) {
|
344
367
|
if (this.props.selectBranchWithChildren) {
|
345
368
|
let filteredItems: Array<T> = [];
|
369
|
+
|
346
370
|
option.children.forEach((item: { value: T; }) => {
|
347
371
|
if (!this.state.value.includes(item.value)) {
|
348
372
|
filteredItems.push(item.value);
|
349
373
|
}
|
350
374
|
});
|
375
|
+
|
351
376
|
this.setState({
|
352
377
|
value: [...this.state.value, ...filteredItems],
|
353
378
|
options: this.state.firstBranchOptions,
|
@@ -357,12 +382,14 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
357
382
|
});
|
358
383
|
} else {
|
359
384
|
let filteredItems: Array<T> = [];
|
385
|
+
|
360
386
|
option.children.forEach((item: ITreeNode<T>) => {
|
361
387
|
if (!this.state.value.includes(item.value)
|
362
388
|
&& !item.children) {
|
363
389
|
filteredItems.push(item.value);
|
364
390
|
}
|
365
391
|
});
|
392
|
+
|
366
393
|
if (filteredItems.length > 0) {
|
367
394
|
this.setState({
|
368
395
|
value: [...this.state.value, ...filteredItems],
|
@@ -376,6 +403,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
376
403
|
}
|
377
404
|
} else {
|
378
405
|
this.handleValue(event, option);
|
406
|
+
|
379
407
|
if (!event.ctrlKey) {
|
380
408
|
this.setState({openDropdown: false});
|
381
409
|
}
|
@@ -393,9 +421,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
393
421
|
options: items,
|
394
422
|
});
|
395
423
|
}
|
396
|
-
}
|
397
424
|
|
398
|
-
backButtonValue = () => {
|
399
425
|
const item = this.state.buttonTree.pop();
|
400
426
|
|
401
427
|
if (item != null) {
|
@@ -408,6 +434,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
408
434
|
recursion(arr: Array<ITreeNode<T>>) {
|
409
435
|
arr.map((item) => {
|
410
436
|
this.state.filterArr.push(item);
|
437
|
+
|
411
438
|
if (item.children) {
|
412
439
|
this.recursion(item.children);
|
413
440
|
}
|
@@ -419,7 +446,9 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
419
446
|
let filteredArr = arr.filter((item) => {
|
420
447
|
if (this.state.searchFieldValue) {
|
421
448
|
if (this.props.getLabel(item.value)
|
422
|
-
|
449
|
+
.toLowerCase()
|
450
|
+
.includes(this.state.searchFieldValue.toLowerCase())
|
451
|
+
) {
|
423
452
|
return item.value;
|
424
453
|
} else {
|
425
454
|
return;
|
@@ -430,59 +459,29 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
430
459
|
});
|
431
460
|
|
432
461
|
if (filteredArr.length === 0) {
|
433
|
-
return <li className="suggestion-item--nothing-found">Nothing
|
462
|
+
return <li className="suggestion-item--nothing-found">Nothing found</li>;
|
434
463
|
} else {
|
435
464
|
return filteredArr.map((option, i) => {
|
436
465
|
let selectedItem = this.state.value.some((obj) =>
|
437
466
|
this.props.getId(obj) === this.props.getId(option.value),
|
438
467
|
);
|
468
|
+
|
439
469
|
return (
|
440
|
-
<
|
470
|
+
<TreeSelectItem
|
441
471
|
key={i}
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
}
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
className="item-border"
|
456
|
-
style={{
|
457
|
-
backgroundColor: this.props.getBorderColor(option.value),
|
458
|
-
}}
|
459
|
-
>
|
460
|
-
</div>
|
461
|
-
}
|
462
|
-
<span
|
463
|
-
className={'suggestion-item--bgcolor'
|
464
|
-
+ (selectedItem ? ' suggestion-item--disabled' : '')
|
465
|
-
}
|
466
|
-
style={this.props.getBackgroundColor
|
467
|
-
? {
|
468
|
-
backgroundColor: this.props.getBackgroundColor(option.value),
|
469
|
-
color: getTextColor(this.props.getBackgroundColor(option.value)),
|
470
|
-
}
|
471
|
-
: undefined
|
472
|
-
}
|
473
|
-
>
|
474
|
-
{this.props.optionTemplate
|
475
|
-
? this.props.optionTemplate(option.value)
|
476
|
-
: this.props.getLabel(option.value)
|
477
|
-
}
|
478
|
-
</span>
|
479
|
-
{option.children
|
480
|
-
&& <span className="suggestion-item__icon">
|
481
|
-
<Icon name="chevron-right-thin"></Icon>
|
482
|
-
</span>
|
483
|
-
}
|
484
|
-
</button>
|
485
|
-
</li>
|
472
|
+
option={option}
|
473
|
+
handleTree={this.handleTree}
|
474
|
+
selectedItem={selectedItem}
|
475
|
+
allowMultiple={this.props.allowMultiple}
|
476
|
+
getBorderColor={this.props.getBorderColor}
|
477
|
+
getBackgroundColor={this.props.getBackgroundColor}
|
478
|
+
getId={this.props.getId}
|
479
|
+
optionTemplate={this.props.optionTemplate}
|
480
|
+
getLabel={this.props.getLabel}
|
481
|
+
onClick={() => this.setState({
|
482
|
+
searchFieldValue: '',
|
483
|
+
})}
|
484
|
+
/>
|
486
485
|
);
|
487
486
|
});
|
488
487
|
}
|
@@ -491,23 +490,25 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
491
490
|
let selectedItem = this.state.value.some((obj) =>
|
492
491
|
this.props.getId(obj) === this.props.getId(item.value),
|
493
492
|
);
|
493
|
+
|
494
494
|
return (
|
495
495
|
<li
|
496
496
|
key={i}
|
497
|
-
className=
|
497
|
+
className='suggestion-item suggestion-item--multi-select'
|
498
498
|
onClick={(event) => {
|
499
|
-
|
499
|
+
this.handleValue(event, item);
|
500
500
|
}}
|
501
501
|
>
|
502
502
|
<button className="suggestion-item--btn">
|
503
503
|
{this.props.optionTemplate
|
504
504
|
? this.props.optionTemplate(item.value)
|
505
|
-
:
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
505
|
+
: (
|
506
|
+
<span
|
507
|
+
className={selectedItem ? 'suggestion-item--disabled' : undefined}
|
508
|
+
>
|
509
|
+
{this.props.getLabel(item.value)}
|
510
|
+
</span>
|
511
|
+
)
|
511
512
|
}
|
512
513
|
</button>
|
513
514
|
</li>
|
@@ -524,15 +525,17 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
524
525
|
if (e.key === 'ArrowDown') {
|
525
526
|
e.preventDefault();
|
526
527
|
e.stopPropagation();
|
528
|
+
|
527
529
|
setTimeout(() => {
|
528
|
-
const element: HTMLElement
|
529
|
-
= document.querySelector('.suggestion-item--btn') as HTMLElement;
|
530
|
+
const element: HTMLElement = document.querySelector('.suggestion-item--btn') as HTMLElement;
|
530
531
|
element.focus();
|
531
532
|
});
|
532
533
|
}
|
534
|
+
|
533
535
|
if (e.key === 'ArrowUp') {
|
534
536
|
e.preventDefault();
|
535
537
|
e.stopPropagation();
|
538
|
+
|
536
539
|
this.inputRef.current?.focus();
|
537
540
|
}
|
538
541
|
});
|
@@ -543,22 +546,25 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
543
546
|
);
|
544
547
|
|
545
548
|
if (!selectedButton) {
|
546
|
-
return
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
549
|
+
return (
|
550
|
+
<button
|
551
|
+
className='autocomplete__button autocomplete__button--multi-select'
|
552
|
+
ref={this.categoryButtonRef}
|
553
|
+
onMouseOver={() => this.setState({buttonMouseEvent: true})}
|
554
|
+
onMouseOut={() => this.setState({buttonMouseEvent: false})}
|
555
|
+
onClick={(event) => this.handleBranchValue(event, buttonValue)}
|
556
|
+
>
|
557
|
+
Choose entire category
|
558
|
+
</button>
|
559
|
+
);
|
555
560
|
} else {
|
556
|
-
return
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
561
|
+
return (
|
562
|
+
<button
|
563
|
+
className='autocomplete__button autocomplete__button--multi-select autocomplete__button--disabled'
|
564
|
+
>
|
565
|
+
Category selected
|
566
|
+
</button>
|
567
|
+
);
|
562
568
|
}
|
563
569
|
}
|
564
570
|
|
@@ -567,11 +573,13 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
567
573
|
|
568
574
|
handleDebounce() {
|
569
575
|
this.setState({options: []});
|
576
|
+
|
570
577
|
if (this.props.kind === 'asynchronous') {
|
571
578
|
if (this.state.searchFieldValue) {
|
572
579
|
this.setState({
|
573
580
|
loading: true,
|
574
581
|
});
|
582
|
+
|
575
583
|
this.ICancelFn = this.props.searchOptions(this.state.searchFieldValue, (items) => {
|
576
584
|
this.setState({options: items, loading: false});
|
577
585
|
this.popperInstance?.update();
|
@@ -613,13 +621,19 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
613
621
|
htmlId={this.htmlId}
|
614
622
|
tabindex={this.props.tabindex}
|
615
623
|
>
|
616
|
-
<div
|
624
|
+
<div
|
625
|
+
className={`tags-input sd-input__input tags-input--${this.props.allowMultiple ? 'multi-select' : 'single-select'}`}
|
626
|
+
ref={this.treeSelectRef}
|
627
|
+
>
|
617
628
|
{this.props.allowMultiple
|
618
629
|
? <div className="tags-input__tags">
|
619
630
|
{this.props.readOnly
|
620
|
-
|| <button
|
631
|
+
|| <button
|
632
|
+
ref={this.openDropdownRef}
|
621
633
|
className={`tags-input__add-button ${this.props.disabled ? 'tags-input__add-button--disabled' : ''}`}
|
622
|
-
onClick={() => {
|
634
|
+
onClick={(e) => {
|
635
|
+
e.stopPropagation();
|
636
|
+
|
623
637
|
if (!this.props.disabled) {
|
624
638
|
this.setState({openDropdown: !this.state.openDropdown});
|
625
639
|
}
|
@@ -628,59 +642,48 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
628
642
|
<i className="icon-plus-large"></i>
|
629
643
|
</button>
|
630
644
|
}
|
645
|
+
|
631
646
|
<ul className="tags-input__tag-list">
|
632
647
|
{this.state.value.map((item, i: number) => {
|
633
648
|
const Wrapper: React.ComponentType<{backgroundColor?: string}>
|
634
649
|
= ({backgroundColor, children}) => (
|
635
|
-
<
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
}
|
641
|
-
|
642
|
-
|
643
|
-
: this.props.getBackgroundColor
|
644
|
-
&& {backgroundColor: this.props.getBackgroundColor(item)}}
|
650
|
+
<TreeSelectPill
|
651
|
+
item={item}
|
652
|
+
readOnly={this.props.readOnly}
|
653
|
+
disabled={this.props.disabled}
|
654
|
+
valueTemplate={this.props.valueTemplate}
|
655
|
+
backgroundColor={backgroundColor}
|
656
|
+
onRemove={() => this.removeClick(i)}
|
657
|
+
getBackgroundColor={this.props.getBackgroundColor}
|
645
658
|
>
|
646
|
-
|
647
|
-
|
648
|
-
style={
|
649
|
-
{color: backgroundColor
|
650
|
-
? getTextColor(backgroundColor)
|
651
|
-
: this.props.getBackgroundColor
|
652
|
-
&& getTextColor(this.props.getBackgroundColor(item)),
|
653
|
-
}
|
654
|
-
}
|
655
|
-
>
|
656
|
-
{children}
|
657
|
-
{!this.props.readOnly
|
658
|
-
&& <span className="tags-input__remove-button">
|
659
|
-
<Icon name="close-small"></Icon>
|
660
|
-
</span>
|
661
|
-
}
|
662
|
-
</span>
|
663
|
-
</li>
|
659
|
+
{children}
|
660
|
+
</TreeSelectPill>
|
664
661
|
);
|
665
662
|
|
666
663
|
return (
|
667
664
|
<React.Fragment key={i}>
|
668
665
|
{this.props.valueTemplate
|
669
666
|
? this.props.valueTemplate(item, Wrapper)
|
670
|
-
:
|
671
|
-
<
|
672
|
-
|
667
|
+
: (
|
668
|
+
<Wrapper>
|
669
|
+
<span>{this.props.getLabel(item)}</span>
|
670
|
+
</Wrapper>
|
671
|
+
)
|
673
672
|
}
|
674
673
|
</React.Fragment>
|
675
674
|
);
|
676
675
|
})}
|
677
676
|
</ul>
|
677
|
+
|
678
678
|
{this.state.value.length > 0
|
679
679
|
? (this.props.readOnly || this.props.disabled)
|
680
680
|
|| <button
|
681
681
|
className="tags-input__remove-value"
|
682
682
|
style={{position: 'relative', bottom: '2px'}}
|
683
|
-
onClick={() =>
|
683
|
+
onClick={(e) => {
|
684
|
+
e.stopPropagation();
|
685
|
+
this.setState({value: []});
|
686
|
+
}}
|
684
687
|
>
|
685
688
|
<Icon name='remove-sign'></Icon>
|
686
689
|
</button>
|
@@ -692,10 +695,12 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
692
695
|
|| <button
|
693
696
|
className="tags-input__overlay-button"
|
694
697
|
ref={this.openDropdownRef}
|
695
|
-
onClick={() =>
|
696
|
-
|
697
|
-
|
698
|
+
onClick={() => {
|
699
|
+
this.setState({openDropdown: !this.state.openDropdown});
|
700
|
+
}}
|
701
|
+
/>
|
698
702
|
}
|
703
|
+
|
699
704
|
{this.state.value.length < 1
|
700
705
|
&& <span
|
701
706
|
className={ 'tags-input__single-item'
|
@@ -707,15 +712,16 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
707
712
|
</span>
|
708
713
|
</span>
|
709
714
|
}
|
715
|
+
|
710
716
|
{this.state.value.map((item, i: number) => {
|
711
717
|
const Wrapper: React.ComponentType<{backgroundColor?: string, borderColor?: string}>
|
712
718
|
= ({backgroundColor, borderColor, children}) => (
|
713
719
|
<span
|
714
|
-
className={
|
720
|
+
className={
|
721
|
+
'tags-input__single-item'
|
715
722
|
+ (this.props.readOnly ? ' tags-input__tag-item--readonly' : '')
|
716
723
|
}
|
717
|
-
onClick={() => !this.props.readOnly && this.removeClick(i)
|
718
|
-
}
|
724
|
+
onClick={() => !this.props.readOnly && this.removeClick(i)}
|
719
725
|
>
|
720
726
|
{this.props.getBorderColor
|
721
727
|
&& <div
|
@@ -727,9 +733,10 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
727
733
|
>
|
728
734
|
</div>
|
729
735
|
}
|
736
|
+
|
730
737
|
<span
|
731
|
-
style={{color: backgroundColor && getTextColor(backgroundColor)}}
|
732
738
|
className="tags-input__helper-box"
|
739
|
+
style={{color: backgroundColor && getTextColor(backgroundColor)}}
|
733
740
|
>
|
734
741
|
<span
|
735
742
|
className={backgroundColor && `tags-input__tag-item`}
|
@@ -737,6 +744,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
737
744
|
>
|
738
745
|
{children}
|
739
746
|
</span>
|
747
|
+
|
740
748
|
{this.props.readOnly
|
741
749
|
|| <span className="tags-input__remove-button">
|
742
750
|
<Icon name='remove-sign'></Icon>
|
@@ -749,9 +757,11 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
749
757
|
return <React.Fragment key={i}>
|
750
758
|
{this.props.valueTemplate
|
751
759
|
? this.props.valueTemplate(item, Wrapper)
|
752
|
-
:
|
753
|
-
<
|
754
|
-
|
760
|
+
: (
|
761
|
+
<Wrapper>
|
762
|
+
<span>{this.props.getLabel(item)}</span>
|
763
|
+
</Wrapper>
|
764
|
+
)
|
755
765
|
}
|
756
766
|
</React.Fragment>;
|
757
767
|
})}
|
@@ -760,7 +770,8 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
760
770
|
|
761
771
|
{this.state.openDropdown
|
762
772
|
&& <div
|
763
|
-
className={
|
773
|
+
className={
|
774
|
+
"autocomplete autocomplete--multi-select"
|
764
775
|
+ (this.props.width === 'medium' ? ' autocomplete--fixed-width' : '')
|
765
776
|
}
|
766
777
|
style={{zIndex: this.props.zIndex}}
|
@@ -770,12 +781,12 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
770
781
|
<div
|
771
782
|
className="autocomplete__icon"
|
772
783
|
onClick={() => {
|
773
|
-
this.backButtonValue();
|
774
784
|
this.backButton();
|
775
785
|
}}
|
776
786
|
>
|
777
787
|
<Icon name="search" className="search"></Icon>
|
778
788
|
</div>
|
789
|
+
|
779
790
|
<div className='autocomplete__filter'>
|
780
791
|
<input
|
781
792
|
className="autocomplete__input"
|
@@ -791,6 +802,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
791
802
|
if (this.ICancelFn) {
|
792
803
|
this.ICancelFn();
|
793
804
|
}
|
805
|
+
|
794
806
|
this.setState({searchFieldValue: event.target.value, options: []});
|
795
807
|
this.popperInstance?.update();
|
796
808
|
this.debounceFn();
|
@@ -801,19 +813,20 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
801
813
|
/>
|
802
814
|
</div>
|
803
815
|
</div>
|
816
|
+
|
804
817
|
{(this.state.activeTree.length > 0 && this.state.buttonValue != null)
|
805
818
|
&& <div className='autocomplete__category-header'>
|
806
819
|
<div
|
807
820
|
className="autocomplete__icon"
|
808
821
|
onClick={() => {
|
809
|
-
this.backButtonValue();
|
810
822
|
this.backButton();
|
811
823
|
}}
|
812
824
|
>
|
813
825
|
<Icon name="arrow-left" className="arrow-left"></Icon>
|
814
826
|
</div>
|
827
|
+
|
815
828
|
<div className='autocomplete__filter'>
|
816
|
-
<button className=
|
829
|
+
<button className='autocomplete__category-title'>
|
817
830
|
{this.props.optionTemplate
|
818
831
|
? this.props.optionTemplate(this.state.buttonValue.value)
|
819
832
|
: this.props.getLabel(this.state.buttonValue.value)
|
@@ -826,95 +839,53 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
826
839
|
</div>
|
827
840
|
</div>
|
828
841
|
}
|
842
|
+
|
829
843
|
{this.state.loading
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
844
|
+
? <ul className="suggestion-list--loader"><Loader overlay={true}></Loader></ul>
|
845
|
+
: this.state.searchFieldValue === ''
|
846
|
+
? this.props.getOptions
|
847
|
+
? <ul
|
848
|
+
className="suggestion-list suggestion-list--multi-select"
|
849
|
+
ref={this.ref}
|
850
|
+
>
|
851
|
+
{this.state.options.map((option, i: React.Key | undefined) => {
|
852
|
+
let selectedItem = this.state.value.some((obj) =>
|
853
|
+
this.props.getId(obj) === this.props.getId(option.value),
|
854
|
+
);
|
855
|
+
|
856
|
+
return (
|
857
|
+
<TreeSelectItem
|
858
|
+
key={i}
|
859
|
+
option={option}
|
860
|
+
handleTree={this.handleTree}
|
861
|
+
selectedItem={selectedItem}
|
862
|
+
allowMultiple={this.props.allowMultiple}
|
863
|
+
getBorderColor={this.props.getBorderColor}
|
864
|
+
getBackgroundColor={this.props.getBackgroundColor}
|
865
|
+
getId={this.props.getId}
|
866
|
+
optionTemplate={this.props.optionTemplate}
|
867
|
+
getLabel={this.props.getLabel}
|
868
|
+
onKeyDown={() => this.setState({
|
869
|
+
buttonTarget: [
|
870
|
+
...this.state.buttonTarget,
|
871
|
+
this.props.getId(option.value),
|
872
|
+
],
|
873
|
+
})}
|
874
|
+
/>
|
875
|
+
);
|
876
|
+
})}
|
877
|
+
</ul>
|
878
|
+
: null
|
879
|
+
: <ul
|
834
880
|
className="suggestion-list suggestion-list--multi-select"
|
835
881
|
ref={this.ref}
|
836
882
|
>
|
837
|
-
{this.
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
<li
|
843
|
-
key={i}
|
844
|
-
className={`suggestion-item suggestion-item--multi-select`}
|
845
|
-
onClick={(event) => {
|
846
|
-
event.preventDefault();
|
847
|
-
event.stopPropagation();
|
848
|
-
this.handleTree(event, option);
|
849
|
-
}}
|
850
|
-
>
|
851
|
-
<button
|
852
|
-
className={`suggestion-item--btn ${this.props.getId(option.value)}-focus`}
|
853
|
-
onKeyDown={(event) => {
|
854
|
-
if (event.key === 'Enter' && option.children) {
|
855
|
-
this.setState({
|
856
|
-
buttonTarget: [
|
857
|
-
...this.state.buttonTarget,
|
858
|
-
this.props.getId(option.value),
|
859
|
-
],
|
860
|
-
});
|
861
|
-
}
|
862
|
-
}}
|
863
|
-
>
|
864
|
-
{(this.props.getBorderColor && !this.props.allowMultiple)
|
865
|
-
&& <div
|
866
|
-
className="item-border"
|
867
|
-
style={{
|
868
|
-
backgroundColor: this.props.getBorderColor(
|
869
|
-
option.value,
|
870
|
-
),
|
871
|
-
}}
|
872
|
-
>
|
873
|
-
</div>
|
874
|
-
}
|
875
|
-
<span
|
876
|
-
className={
|
877
|
-
'suggestion-item--bgcolor'
|
878
|
-
+ (selectedItem ? ' suggestion-item--disabled' : '')
|
879
|
-
}
|
880
|
-
style={
|
881
|
-
(this.props.getBackgroundColor && option.value)
|
882
|
-
? {
|
883
|
-
backgroundColor:
|
884
|
-
this.props.getBackgroundColor(option.value),
|
885
|
-
color:
|
886
|
-
getTextColor(this.props.getBackgroundColor(
|
887
|
-
option.value,
|
888
|
-
),
|
889
|
-
),
|
890
|
-
}
|
891
|
-
: undefined
|
892
|
-
}
|
893
|
-
>
|
894
|
-
{this.props.optionTemplate
|
895
|
-
? this.props.optionTemplate(option.value)
|
896
|
-
: this.props.getLabel(option.value)
|
897
|
-
}
|
898
|
-
</span>
|
899
|
-
{option.children
|
900
|
-
&& <span className="suggestion-item__icon">
|
901
|
-
<Icon name="chevron-right-thin"></Icon>
|
902
|
-
</span>
|
903
|
-
}
|
904
|
-
</button>
|
905
|
-
</li>
|
906
|
-
);
|
907
|
-
})}
|
883
|
+
{this.filteredItem(
|
884
|
+
this.props.singleLevelSearch
|
885
|
+
? this.state.options
|
886
|
+
: this.state.filterArr,
|
887
|
+
)}
|
908
888
|
</ul>
|
909
|
-
: null
|
910
|
-
: <ul
|
911
|
-
className="suggestion-list suggestion-list--multi-select"
|
912
|
-
ref={this.ref}
|
913
|
-
>
|
914
|
-
{this.filteredItem(this.props.singleLevelSearch
|
915
|
-
? this.state.options : this.state.filterArr)
|
916
|
-
}
|
917
|
-
</ul>
|
918
889
|
}
|
919
890
|
</div>
|
920
891
|
}
|