superdesk-ui-framework 3.0.6 → 3.0.8
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/styles/_sd-tag-input.scss +29 -10
- package/app-typescript/components/Lists/ContentList.tsx +28 -27
- package/app-typescript/components/Lists/TableList.tsx +140 -126
- package/app-typescript/components/MultiSelect.tsx +2 -3
- package/app-typescript/components/SingleAndDoubleClickFunction.tsx +21 -0
- package/app-typescript/components/TreeSelect.tsx +506 -243
- package/dist/examples.bundle.js +1468 -1186
- package/dist/playgrounds/react-playgrounds/EditorTest.tsx +18 -10
- package/dist/playgrounds/react-playgrounds/Multiedit.tsx +15 -13
- package/dist/playgrounds/react-playgrounds/RundownEditor.tsx +136 -125
- package/dist/react/Dropdowns.tsx +134 -85
- package/dist/react/MultiSelect.tsx +2 -2
- package/dist/react/TreeSelect.tsx +39 -27
- package/dist/superdesk-ui.bundle.css +26 -9
- package/dist/superdesk-ui.bundle.js +1015 -809
- package/dist/vendor.bundle.js +14 -14
- package/examples/pages/playgrounds/react-playgrounds/EditorTest.tsx +18 -10
- package/examples/pages/playgrounds/react-playgrounds/Multiedit.tsx +15 -13
- package/examples/pages/playgrounds/react-playgrounds/RundownEditor.tsx +136 -125
- package/examples/pages/react/Dropdowns.tsx +134 -85
- package/examples/pages/react/MultiSelect.tsx +2 -2
- package/examples/pages/react/TreeSelect.tsx +39 -27
- package/package.json +2 -2
- package/react/components/Lists/ContentList.d.ts +2 -5
- package/react/components/Lists/ContentList.js +20 -25
- package/react/components/Lists/TableList.d.ts +4 -7
- package/react/components/Lists/TableList.js +66 -60
- package/react/components/MultiSelect.d.ts +1 -1
- package/react/components/MultiSelect.js +1 -1
- package/react/components/SingleAndDoubleClickFunction.d.ts +6 -0
- package/react/components/SingleAndDoubleClickFunction.js +19 -0
- package/react/components/TreeSelect.d.ts +17 -9
- package/react/components/TreeSelect.js +249 -74
@@ -4,23 +4,23 @@ import { Loader } from "./Loader";
|
|
4
4
|
import nextId from "react-id-generator";
|
5
5
|
import _debounce from 'lodash/debounce';
|
6
6
|
import { InputWrapper } from "./Form";
|
7
|
-
import { createPopper } from '@popperjs/core';
|
7
|
+
import { createPopper, Instance } from '@popperjs/core';
|
8
8
|
import {isEqual} from 'lodash';
|
9
9
|
import {getTextColor} from './Label';
|
10
10
|
|
11
11
|
interface IState<T> {
|
12
12
|
value: Array<T>;
|
13
13
|
options: Array<ITreeNode<T>>;
|
14
|
-
firstBranchOptions: Array<
|
14
|
+
firstBranchOptions: Array<ITreeNode<T>>;
|
15
15
|
openDropdown: boolean;
|
16
|
-
activeTree: Array<
|
17
|
-
filterArr: Array<
|
16
|
+
activeTree: Array<Array<ITreeNode<T>>>;
|
17
|
+
filterArr: Array<ITreeNode<T>>;
|
18
18
|
searchFieldValue: string;
|
19
|
-
buttonTree: Array<
|
20
|
-
buttonValue:
|
19
|
+
buttonTree: Array<ITreeNode<T>>;
|
20
|
+
buttonValue: ITreeNode<T> | null;
|
21
21
|
buttonMouseEvent: boolean;
|
22
22
|
loading: boolean;
|
23
|
-
//
|
23
|
+
buttonTarget: Array<string>; // array of class names
|
24
24
|
}
|
25
25
|
|
26
26
|
interface IPropsBase<T> {
|
@@ -48,7 +48,7 @@ interface IPropsBase<T> {
|
|
48
48
|
getBackgroundColor?(item: T): string;
|
49
49
|
getBorderColor?(item: T): string;
|
50
50
|
optionTemplate?(item: T): React.ComponentType<T> | JSX.Element;
|
51
|
-
valueTemplate?(item: T, Wrapper:
|
51
|
+
valueTemplate?(item: T, Wrapper: React.ElementType): React.ComponentType<T> | JSX.Element;
|
52
52
|
onChange(e: Array<T>): void;
|
53
53
|
}
|
54
54
|
|
@@ -57,10 +57,12 @@ interface IPropsSync<T> extends IPropsBase<T> {
|
|
57
57
|
getOptions(): Array<ITreeNode<T>>;
|
58
58
|
}
|
59
59
|
|
60
|
+
type ICancelFn = () => void;
|
61
|
+
|
60
62
|
interface IPropsAsync<T> extends IPropsBase<T> {
|
61
63
|
kind: 'asynchronous';
|
62
64
|
getOptions?(): Array<ITreeNode<T>>;
|
63
|
-
searchOptions(term: string, callback?: (options: Array<ITreeNode<T>>) => void):
|
65
|
+
searchOptions(term: string, callback?: (options: Array<ITreeNode<T>>) => void): ICancelFn;
|
64
66
|
}
|
65
67
|
|
66
68
|
type IProps<T> = IPropsSync<T> | IPropsAsync<T>;
|
@@ -72,9 +74,12 @@ export interface ITreeNode<T> {
|
|
72
74
|
|
73
75
|
export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
74
76
|
private dropdownRef: React.RefObject<HTMLInputElement>;
|
77
|
+
private ref: React.RefObject<HTMLUListElement>;
|
78
|
+
private inputRef: React.RefObject<HTMLInputElement>;
|
79
|
+
private categoryButtonRef: React.RefObject<HTMLButtonElement>;
|
75
80
|
private openDropdownRef: React.RefObject<HTMLButtonElement>;
|
76
81
|
private htmlId: string = nextId();
|
77
|
-
private popperInstance:
|
82
|
+
private popperInstance: Instance | null;
|
78
83
|
|
79
84
|
constructor(props: IProps<T>) {
|
80
85
|
super(props);
|
@@ -86,11 +91,11 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
86
91
|
filterArr: [],
|
87
92
|
searchFieldValue: '',
|
88
93
|
buttonTree: [],
|
89
|
-
buttonValue:
|
94
|
+
buttonValue: null,
|
90
95
|
buttonMouseEvent: false,
|
91
96
|
openDropdown: false,
|
92
97
|
loading: false,
|
93
|
-
|
98
|
+
buttonTarget: [],
|
94
99
|
};
|
95
100
|
|
96
101
|
this.removeClick = this.removeClick.bind(this);
|
@@ -100,21 +105,61 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
100
105
|
this.backButtonValue = this.backButtonValue.bind(this);
|
101
106
|
this.handleTree = this.handleTree.bind(this);
|
102
107
|
this.filteredItem = this.filteredItem.bind(this);
|
103
|
-
this.
|
108
|
+
this.branchButton = this.branchButton.bind(this);
|
104
109
|
this.handleDebounce = this.handleDebounce.bind(this);
|
105
110
|
this.toggleMenu = this.toggleMenu.bind(this);
|
106
111
|
this.dropdownRef = React.createRef();
|
112
|
+
this.ref = React.createRef();
|
113
|
+
this.inputRef = React.createRef();
|
114
|
+
this.categoryButtonRef = React.createRef();
|
107
115
|
this.openDropdownRef = React.createRef();
|
116
|
+
|
117
|
+
this.popperInstance = null;
|
118
|
+
}
|
119
|
+
|
120
|
+
inputFocus = () => {
|
121
|
+
this.inputRef.current?.focus();
|
122
|
+
}
|
123
|
+
listNavigation = () => {
|
124
|
+
const element: HTMLElement = document.querySelector('.suggestion-item--btn') as HTMLElement;
|
125
|
+
element.focus();
|
126
|
+
}
|
127
|
+
buttonFocus = () => {
|
128
|
+
this.categoryButtonRef.current?.focus();
|
108
129
|
}
|
109
130
|
|
110
131
|
componentDidMount = () => {
|
111
132
|
this.recursion(this.state.options);
|
112
|
-
document.addEventListener("mousedown", (event
|
133
|
+
document.addEventListener("mousedown", (event) => {
|
134
|
+
if (!(event.target instanceof HTMLInputElement)) {
|
135
|
+
return;
|
136
|
+
}
|
113
137
|
if ((this.dropdownRef.current && !this.dropdownRef.current.contains(event.target))
|
114
138
|
&& (this.openDropdownRef.current && !this.openDropdownRef.current.contains(event.target))) {
|
115
139
|
this.setState({openDropdown: false});
|
116
140
|
}
|
117
141
|
});
|
142
|
+
|
143
|
+
document.addEventListener("keydown", (e: KeyboardEvent) => {
|
144
|
+
if (this.state.openDropdown && this.ref.current) {
|
145
|
+
keyboardNavigation(
|
146
|
+
e,
|
147
|
+
this.ref.current,
|
148
|
+
this.categoryButtonRef.current ? this.buttonFocus : this.inputFocus,
|
149
|
+
);
|
150
|
+
if (e.key === 'Backspace') {
|
151
|
+
this.backButton();
|
152
|
+
|
153
|
+
const {buttonTarget} = this.state;
|
154
|
+
const className = buttonTarget.pop();
|
155
|
+
|
156
|
+
if (className != null) {
|
157
|
+
const element: HTMLElement = document.getElementsByClassName(className)[0] as HTMLElement;
|
158
|
+
element.focus();
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
});
|
118
163
|
}
|
119
164
|
|
120
165
|
componentDidUpdate(prevProps: Readonly<IProps<T>>, prevState: Readonly<IState<T>>): void {
|
@@ -130,7 +175,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
130
175
|
if ((prevState.activeTree !== this.state.activeTree)
|
131
176
|
|| (prevState.filterArr !== this.state.filterArr)
|
132
177
|
|| (prevState.options !== this.state.options)) {
|
133
|
-
this.popperInstance
|
178
|
+
this.popperInstance?.update();
|
134
179
|
}
|
135
180
|
}
|
136
181
|
}
|
@@ -150,6 +195,28 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
150
195
|
],
|
151
196
|
});
|
152
197
|
}
|
198
|
+
this.inputRef.current?.addEventListener('keydown', (e: KeyboardEvent) => {
|
199
|
+
if (e.key === 'ArrowDown') {
|
200
|
+
e.preventDefault();
|
201
|
+
e.stopPropagation();
|
202
|
+
|
203
|
+
if (this.categoryButtonRef.current) {
|
204
|
+
this.buttonFocus();
|
205
|
+
} else {
|
206
|
+
setTimeout(() => {
|
207
|
+
this.listNavigation();
|
208
|
+
});
|
209
|
+
}
|
210
|
+
}
|
211
|
+
});
|
212
|
+
if (this.inputRef.current) {
|
213
|
+
this.inputFocus();
|
214
|
+
} else {
|
215
|
+
const element: HTMLElement = document.querySelector('.suggestion-item--btn') as HTMLElement;
|
216
|
+
element.focus();
|
217
|
+
}
|
218
|
+
} else {
|
219
|
+
this.openDropdownRef.current?.focus();
|
153
220
|
}
|
154
221
|
}
|
155
222
|
|
@@ -173,8 +240,14 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
173
240
|
}
|
174
241
|
|
175
242
|
handleButton(item: ITreeNode<T>) {
|
243
|
+
let buttonTreeNext: Array<ITreeNode<T>> = this.state.buttonTree;
|
244
|
+
|
245
|
+
if (this.state.buttonValue != null) {
|
246
|
+
buttonTreeNext = buttonTreeNext.concat(this.state.buttonValue);
|
247
|
+
}
|
248
|
+
|
176
249
|
this.setState({
|
177
|
-
buttonTree:
|
250
|
+
buttonTree: buttonTreeNext,
|
178
251
|
buttonValue: item,
|
179
252
|
});
|
180
253
|
}
|
@@ -189,9 +262,14 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
189
262
|
}
|
190
263
|
if (!event.ctrlKey) {
|
191
264
|
if (this.props.getOptions) {
|
192
|
-
this.setState({
|
265
|
+
this.setState({
|
266
|
+
options: this.state.firstBranchOptions,
|
267
|
+
activeTree: [],
|
268
|
+
buttonTarget: [],
|
269
|
+
openDropdown: false,
|
270
|
+
});
|
193
271
|
} else {
|
194
|
-
this.setState({activeTree: [], openDropdown: false});
|
272
|
+
this.setState({activeTree: [], buttonTarget: [], openDropdown: false});
|
195
273
|
}
|
196
274
|
}
|
197
275
|
this.setState({buttonMouseEvent: false});
|
@@ -203,7 +281,13 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
203
281
|
this.setState({value: [item.value]});
|
204
282
|
}
|
205
283
|
if (!event.ctrlKey) {
|
206
|
-
this.setState({
|
284
|
+
this.setState({
|
285
|
+
options: this.state.firstBranchOptions,
|
286
|
+
activeTree: [],
|
287
|
+
buttonTarget:
|
288
|
+
[],
|
289
|
+
openDropdown: false,
|
290
|
+
});
|
207
291
|
}
|
208
292
|
this.setState({buttonMouseEvent: false});
|
209
293
|
}
|
@@ -219,7 +303,12 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
219
303
|
this.setState({value: [...this.state.value, item.value]});
|
220
304
|
}
|
221
305
|
if (!event.ctrlKey) {
|
222
|
-
this.setState({
|
306
|
+
this.setState({
|
307
|
+
options: this.state.firstBranchOptions,
|
308
|
+
activeTree: [],
|
309
|
+
buttonTarget: [],
|
310
|
+
openDropdown: false,
|
311
|
+
});
|
223
312
|
}
|
224
313
|
this.setState({buttonMouseEvent: false});
|
225
314
|
}
|
@@ -232,7 +321,12 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
232
321
|
this.setState({value: [item.value]});
|
233
322
|
}
|
234
323
|
if (!event.ctrlKey) {
|
235
|
-
this.setState({
|
324
|
+
this.setState({
|
325
|
+
options: this.state.firstBranchOptions,
|
326
|
+
activeTree: [],
|
327
|
+
buttonTarget: [],
|
328
|
+
openDropdown: false,
|
329
|
+
});
|
236
330
|
}
|
237
331
|
this.setState({buttonMouseEvent: false});
|
238
332
|
}
|
@@ -256,6 +350,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
256
350
|
options: this.state.firstBranchOptions,
|
257
351
|
openDropdown: false,
|
258
352
|
activeTree: [],
|
353
|
+
buttonTarget: [],
|
259
354
|
});
|
260
355
|
} else {
|
261
356
|
let filteredItems: Array<T> = [];
|
@@ -271,6 +366,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
271
366
|
options: this.state.firstBranchOptions,
|
272
367
|
openDropdown: false,
|
273
368
|
activeTree: [],
|
369
|
+
buttonTarget: [],
|
274
370
|
});
|
275
371
|
}
|
276
372
|
}
|
@@ -281,23 +377,29 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
281
377
|
this.setState({openDropdown: false});
|
282
378
|
}
|
283
379
|
}
|
380
|
+
|
381
|
+
const element: HTMLElement = document.querySelector('.suggestion-item--btn') as HTMLElement;
|
382
|
+
element.focus();
|
284
383
|
}
|
285
384
|
|
286
|
-
backButton
|
287
|
-
|
385
|
+
backButton(): void {
|
386
|
+
const items = this.state.activeTree.pop();
|
387
|
+
|
388
|
+
if (items != null) {
|
288
389
|
this.setState({
|
289
|
-
options:
|
390
|
+
options: items,
|
290
391
|
});
|
291
|
-
return;
|
292
|
-
} else {
|
293
|
-
return false;
|
294
392
|
}
|
295
393
|
}
|
296
394
|
|
297
395
|
backButtonValue = () => {
|
298
|
-
this.
|
299
|
-
|
300
|
-
|
396
|
+
const item = this.state.buttonTree.pop();
|
397
|
+
|
398
|
+
if (item != null) {
|
399
|
+
this.setState({
|
400
|
+
buttonValue: item,
|
401
|
+
});
|
402
|
+
}
|
301
403
|
}
|
302
404
|
|
303
405
|
recursion(arr: Array<ITreeNode<T>>) {
|
@@ -331,35 +433,45 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
331
433
|
let selectedItem = this.state.value.some((obj) =>
|
332
434
|
this.props.getId(obj) === this.props.getId(option.value),
|
333
435
|
);
|
334
|
-
return
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
style={{backgroundColor: this.props.getBorderColor(option.value)}}></div>}
|
347
|
-
<span
|
348
|
-
style={this.props.getBackgroundColor
|
349
|
-
? {backgroundColor: this.props.getBackgroundColor(option.value),
|
350
|
-
color: getTextColor(this.props.getBackgroundColor(option.value))}
|
351
|
-
: undefined}
|
352
|
-
className={'suggestion-item--bgcolor'
|
353
|
-
+ (selectedItem ? ' suggestion-item--disabled' : '')}
|
436
|
+
return (
|
437
|
+
<li
|
438
|
+
key={i}
|
439
|
+
className={`suggestion-item suggestion-item--multi-select`}
|
440
|
+
onClick={(event) => {
|
441
|
+
this.setState({
|
442
|
+
searchFieldValue: '',
|
443
|
+
}),
|
444
|
+
event.preventDefault();
|
445
|
+
event.stopPropagation();
|
446
|
+
this.handleTree(event, option);
|
447
|
+
}}
|
354
448
|
>
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
449
|
+
<button className="suggestion-item--btn">
|
450
|
+
{this.props.getBorderColor
|
451
|
+
&& <div className="item-border"
|
452
|
+
style={{backgroundColor: this.props.getBorderColor(option.value)}}>
|
453
|
+
</div>
|
454
|
+
}
|
455
|
+
<span
|
456
|
+
style={this.props.getBackgroundColor
|
457
|
+
? {backgroundColor: this.props.getBackgroundColor(option.value),
|
458
|
+
color: getTextColor(this.props.getBackgroundColor(option.value))}
|
459
|
+
: undefined}
|
460
|
+
className={'suggestion-item--bgcolor'
|
461
|
+
+ (selectedItem ? ' suggestion-item--disabled' : '')}
|
462
|
+
>
|
463
|
+
{this.props.optionTemplate
|
464
|
+
? this.props.optionTemplate(option.value)
|
465
|
+
: this.props.getLabel(option.value)}
|
466
|
+
</span>
|
467
|
+
{option.children
|
468
|
+
&& <span className="suggestion-item__icon">
|
469
|
+
<Icon name="chevron-right-thin"></Icon>
|
470
|
+
</span>
|
471
|
+
}
|
472
|
+
</button>
|
473
|
+
</li>
|
474
|
+
);
|
363
475
|
});
|
364
476
|
}
|
365
477
|
} else if (this.props.kind === 'asynchronous') {
|
@@ -369,18 +481,21 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
369
481
|
);
|
370
482
|
return (
|
371
483
|
<li key={i}
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
484
|
+
className={`suggestion-item suggestion-item--multi-select`}
|
485
|
+
onClick={(event) => {
|
486
|
+
this.handleValue(event, item);
|
487
|
+
}}
|
488
|
+
>
|
489
|
+
<button className="suggestion-item--btn">
|
490
|
+
{this.props.optionTemplate
|
491
|
+
? this.props.optionTemplate(item.value)
|
492
|
+
: <span
|
493
|
+
className={selectedItem
|
494
|
+
? 'suggestion-item--disabled' : undefined}
|
495
|
+
>
|
496
|
+
{this.props.getLabel(item.value)}
|
497
|
+
</span>}
|
498
|
+
</button>
|
384
499
|
</li>
|
385
500
|
);
|
386
501
|
});
|
@@ -389,31 +504,52 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
389
504
|
}
|
390
505
|
}
|
391
506
|
|
392
|
-
|
507
|
+
branchButton(buttonValue: ITreeNode<T>) {
|
508
|
+
setTimeout(() => {
|
509
|
+
this.categoryButtonRef.current?.addEventListener('keydown', (e: KeyboardEvent) => {
|
510
|
+
if (e.key === 'ArrowDown') {
|
511
|
+
e.preventDefault();
|
512
|
+
e.stopPropagation();
|
513
|
+
setTimeout(() => {
|
514
|
+
const element: HTMLElement
|
515
|
+
= document.querySelector('.suggestion-item--btn') as HTMLElement;
|
516
|
+
element.focus();
|
517
|
+
});
|
518
|
+
}
|
519
|
+
if (e.key === 'ArrowUp') {
|
520
|
+
e.preventDefault();
|
521
|
+
e.stopPropagation();
|
522
|
+
this.inputRef.current?.focus();
|
523
|
+
}
|
524
|
+
});
|
525
|
+
});
|
526
|
+
|
393
527
|
let selectedButton = this.state.value.some((obj) =>
|
394
|
-
this.props.getId(obj) === this.props.getId(
|
528
|
+
this.props.getId(obj) === this.props.getId(buttonValue.value),
|
395
529
|
);
|
396
530
|
|
397
531
|
if (!selectedButton) {
|
398
532
|
return <button
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
533
|
+
ref={this.categoryButtonRef}
|
534
|
+
className={'autocomplete__button' + (this.props.selectBranchWithChildren ? ' autocomplete__button--multi-select' : '')}
|
535
|
+
onMouseOver={() => this.setState({buttonMouseEvent: true})}
|
536
|
+
onMouseOut={() => this.setState({buttonMouseEvent: false})}
|
537
|
+
onClick={(event) => this.handleBranchValue(event, buttonValue)}
|
538
|
+
>
|
404
539
|
Choose entire category
|
405
540
|
</button>;
|
406
541
|
} else {
|
407
542
|
return <button
|
408
|
-
|
409
|
-
|
543
|
+
className={'autocomplete__button'
|
544
|
+
+ (this.props.selectBranchWithChildren ? ' autocomplete__button--multi-select' : '') + ' autocomplete__button--disabled'}
|
545
|
+
>
|
410
546
|
Category selected
|
411
547
|
</button>;
|
412
548
|
}
|
413
549
|
}
|
414
550
|
|
415
551
|
private debounceFn = _debounce(this.handleDebounce, 500);
|
416
|
-
private ICancelFn:
|
552
|
+
private ICancelFn: ICancelFn | undefined;
|
417
553
|
|
418
554
|
handleDebounce() {
|
419
555
|
this.setState({options: []});
|
@@ -421,15 +557,10 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
421
557
|
if (this.state.searchFieldValue) {
|
422
558
|
this.setState({
|
423
559
|
loading: true,
|
424
|
-
// provera: false
|
425
560
|
});
|
426
561
|
this.ICancelFn = this.props.searchOptions(this.state.searchFieldValue, (items) => {
|
427
|
-
|
428
|
-
|
429
|
-
// } else {
|
430
|
-
this.setState({options: items, loading: false});
|
431
|
-
this.popperInstance.update();
|
432
|
-
// }
|
562
|
+
this.setState({options: items, loading: false});
|
563
|
+
this.popperInstance?.update();
|
433
564
|
});
|
434
565
|
}
|
435
566
|
}
|
@@ -438,38 +569,40 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
438
569
|
render() {
|
439
570
|
return (
|
440
571
|
<InputWrapper
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
572
|
+
label={this.props.label}
|
573
|
+
error={this.props.error}
|
574
|
+
required={this.props.required}
|
575
|
+
disabled={this.props.disabled}
|
576
|
+
invalid={this.props.invalid}
|
577
|
+
info={this.props.info}
|
578
|
+
inlineLabel={this.props.inlineLabel}
|
579
|
+
labelHidden={this.props.labelHidden}
|
580
|
+
fullWidth={this.props.fullWidth}
|
581
|
+
htmlId={this.htmlId}
|
582
|
+
tabindex={this.props.tabindex}>
|
452
583
|
<div className={`tags-input tags-input--${this.props.allowMultiple ? 'multi-select' : 'single-select'} sd-input__input`}>
|
453
584
|
{this.props.allowMultiple
|
454
585
|
? <div className="tags-input__tags">
|
455
586
|
{this.props.readOnly
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
587
|
+
|| <button ref={this.openDropdownRef}
|
588
|
+
className="tags-input__add-button"
|
589
|
+
onClick={() => this.setState({openDropdown: !this.state.openDropdown})}
|
590
|
+
>
|
591
|
+
<i className="icon-plus-large"></i>
|
592
|
+
</button>}
|
461
593
|
<ul className="tags-input__tag-list">
|
462
594
|
{this.state.value.map((item, i: number) => {
|
463
595
|
const Wrapper: React.ComponentType<{backgroundColor?: string}>
|
464
596
|
= ({backgroundColor, children}) => (
|
465
597
|
<li
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
598
|
+
className={"tags-input__tag-item tags-input__tag-item--multi-select"
|
599
|
+
+ (this.props.readOnly ? ' tags-input__tag-item--readonly' : '')}
|
600
|
+
onClick={() => !this.props.readOnly && this.removeClick(i)}
|
601
|
+
style={this.props.valueTemplate
|
602
|
+
? {backgroundColor}
|
603
|
+
: this.props.getBackgroundColor
|
604
|
+
&& {backgroundColor: this.props.getBackgroundColor(item)}}
|
605
|
+
>
|
473
606
|
<span
|
474
607
|
style={{color: backgroundColor
|
475
608
|
? getTextColor(backgroundColor)
|
@@ -487,10 +620,10 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
487
620
|
return (
|
488
621
|
<React.Fragment key={i}>
|
489
622
|
{this.props.valueTemplate
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
623
|
+
? this.props.valueTemplate(item, Wrapper)
|
624
|
+
: <Wrapper>
|
625
|
+
<span>{this.props.getLabel(item)}</span>
|
626
|
+
</Wrapper>
|
494
627
|
}
|
495
628
|
</React.Fragment>
|
496
629
|
);
|
@@ -498,59 +631,70 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
498
631
|
</ul>
|
499
632
|
{this.state.value.length > 0
|
500
633
|
? this.props.readOnly
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
634
|
+
|| <button className="tags-input__remove-value"
|
635
|
+
style={{position: 'relative', bottom: '2px'}}
|
636
|
+
onClick={() => this.setState({value: []})}>
|
637
|
+
<Icon name='remove-sign'></Icon>
|
638
|
+
</button>
|
639
|
+
: null
|
640
|
+
}
|
506
641
|
</div>
|
507
642
|
: <div className="tags-input__tags">
|
508
643
|
{this.props.readOnly
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
644
|
+
|| <button
|
645
|
+
className="tags-input__overlay-button"
|
646
|
+
ref={this.openDropdownRef}
|
647
|
+
onClick={() => this.setState({openDropdown: !this.state.openDropdown})}
|
648
|
+
>
|
649
|
+
</button>
|
650
|
+
}
|
651
|
+
{this.state.value.length < 1
|
652
|
+
&& <span className={ 'tags-input__single-item'
|
653
|
+
+ (this.props.readOnly ? ' tags-input__tag-item--readonly' : '')}>
|
654
|
+
<span className="tags-input__placeholder">
|
655
|
+
{this.props.placeholder}
|
656
|
+
</span>
|
518
657
|
</span>
|
519
|
-
|
658
|
+
}
|
520
659
|
{this.state.value.map((item, i: number) => {
|
521
660
|
const Wrapper: React.ComponentType<{backgroundColor?: string, borderColor?: string}>
|
522
661
|
= ({backgroundColor, borderColor, children}) => (
|
523
662
|
<span
|
524
|
-
|
525
|
-
|
526
|
-
|
663
|
+
className={ 'tags-input__single-item'
|
664
|
+
+ (this.props.readOnly ? ' tags-input__tag-item--readonly' : '')}
|
665
|
+
onClick={() => this.props.readOnly || this.removeClick(i)}
|
666
|
+
>
|
527
667
|
{this.props.getBorderColor
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
668
|
+
&& <div className="item-border item-border-selected"
|
669
|
+
style={borderColor
|
670
|
+
? {backgroundColor: borderColor}
|
671
|
+
: {backgroundColor: this.props.getBorderColor(item)}}
|
672
|
+
>
|
673
|
+
</div>
|
674
|
+
}
|
532
675
|
<span
|
533
|
-
|
534
|
-
|
676
|
+
style={{color: backgroundColor && getTextColor(backgroundColor)}}
|
677
|
+
className="tags-input__helper-box">
|
535
678
|
<span
|
536
|
-
|
537
|
-
|
679
|
+
className={backgroundColor && `tags-input__tag-item`}
|
680
|
+
style={{backgroundColor, margin: 0}}>
|
538
681
|
{children}
|
539
682
|
</span>
|
540
683
|
{this.props.readOnly
|
541
|
-
|
542
|
-
|
543
|
-
|
684
|
+
|| <span className="tags-input__remove-button">
|
685
|
+
<Icon name='remove-sign'></Icon>
|
686
|
+
</span>
|
687
|
+
}
|
544
688
|
</span>
|
545
689
|
</span>
|
546
690
|
);
|
547
691
|
|
548
692
|
return <React.Fragment key={i}>
|
549
693
|
{this.props.valueTemplate
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
694
|
+
? this.props.valueTemplate(item, Wrapper)
|
695
|
+
: <Wrapper>
|
696
|
+
<span>{this.props.getLabel(item)}</span>
|
697
|
+
</Wrapper>
|
554
698
|
}
|
555
699
|
</React.Fragment>;
|
556
700
|
})}
|
@@ -558,107 +702,226 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
|
|
558
702
|
}
|
559
703
|
|
560
704
|
{this.state.openDropdown
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
705
|
+
&& <div
|
706
|
+
className={"autocomplete autocomplete--multi-select" + (this.props.width === 'medium' ? ' autocomplete--fixed-width' : '')}
|
707
|
+
ref={this.dropdownRef}
|
708
|
+
>
|
709
|
+
<div className='autocomplete__header'>
|
710
|
+
<div
|
711
|
+
className="autocomplete__icon"
|
712
|
+
onClick={() => {
|
713
|
+
this.backButtonValue();
|
714
|
+
this.backButton();
|
715
|
+
}}
|
716
|
+
>
|
717
|
+
<Icon name="search" className="search"></Icon>
|
718
|
+
</div>
|
719
|
+
<div className='autocomplete__filter'>
|
720
|
+
<input
|
721
|
+
placeholder={this.props.searchPlaceholder}
|
722
|
+
type="text"
|
723
|
+
className="autocomplete__input"
|
724
|
+
ref={this.inputRef}
|
725
|
+
value={this.state.searchFieldValue}
|
726
|
+
onChange={(event) => {
|
727
|
+
if (this.props.kind === 'synchronous') {
|
728
|
+
this.setState({searchFieldValue: event.target.value});
|
729
|
+
this.popperInstance?.update();
|
730
|
+
} else if (this.props.kind === 'asynchronous') {
|
731
|
+
if (this.ICancelFn) {
|
732
|
+
this.ICancelFn();
|
733
|
+
}
|
734
|
+
this.setState({searchFieldValue: event.target.value, options: []});
|
735
|
+
this.popperInstance?.update();
|
736
|
+
this.debounceFn();
|
737
|
+
} else {
|
738
|
+
return;
|
739
|
+
}
|
740
|
+
}}
|
741
|
+
/>
|
742
|
+
</div>
|
568
743
|
</div>
|
569
|
-
|
570
|
-
<
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
744
|
+
{(this.state.activeTree.length > 0 && this.state.buttonValue != null)
|
745
|
+
&& <div className='autocomplete__category-header'>
|
746
|
+
<div
|
747
|
+
className="autocomplete__icon"
|
748
|
+
onClick={() => {
|
749
|
+
this.backButtonValue();
|
750
|
+
this.backButton();
|
751
|
+
}}
|
752
|
+
>
|
753
|
+
<Icon name="arrow-left" className="arrow-left"></Icon>
|
754
|
+
</div>
|
755
|
+
<div className='autocomplete__filter'>
|
756
|
+
<button className={'autocomplete__category-title'}>
|
757
|
+
{
|
758
|
+
this.props.optionTemplate
|
759
|
+
? this.props.optionTemplate(this.state.buttonValue.value)
|
760
|
+
: this.props.getLabel(this.state.buttonValue.value)
|
761
|
+
}
|
762
|
+
</button>
|
763
|
+
|
764
|
+
{this.props.selectBranchWithChildren
|
765
|
+
&& this.branchButton(this.state.buttonValue)
|
583
766
|
}
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
767
|
+
</div>
|
768
|
+
</div>
|
769
|
+
}
|
770
|
+
{this.state.loading
|
771
|
+
? <ul className="suggestion-list--loader"><Loader overlay={true}></Loader></ul>
|
772
|
+
: this.state.searchFieldValue === ''
|
773
|
+
? this.props.getOptions
|
774
|
+
? <ul
|
775
|
+
className="suggestion-list suggestion-list--multi-select"
|
776
|
+
ref={this.ref}
|
777
|
+
>
|
778
|
+
{this.state.options
|
779
|
+
.map((option, i: React.Key | undefined) => {
|
780
|
+
let selectedItem = this.state.value.some((obj) =>
|
781
|
+
this.props.getId(obj) === this.props.getLabel(option.value),
|
782
|
+
);
|
783
|
+
return (
|
784
|
+
<li
|
785
|
+
key={i}
|
786
|
+
className={`suggestion-item suggestion-item--multi-select`}
|
787
|
+
onClick={(event) => {
|
788
|
+
event.preventDefault();
|
789
|
+
event.stopPropagation();
|
790
|
+
this.handleTree(event, option);
|
791
|
+
}}
|
792
|
+
>
|
793
|
+
<button
|
794
|
+
className={`suggestion-item--btn ${this.props.getId(option.value)}`}
|
795
|
+
onKeyDown={(event) => {
|
796
|
+
if (event.key === 'Enter' && option.children) {
|
797
|
+
this.setState({
|
798
|
+
buttonTarget: [
|
799
|
+
...this.state.buttonTarget,
|
800
|
+
this.props.getId(option.value),
|
801
|
+
],
|
802
|
+
});
|
803
|
+
}
|
804
|
+
}}
|
805
|
+
>
|
806
|
+
{(this.props.getBorderColor && !this.props.allowMultiple)
|
807
|
+
&& <div
|
808
|
+
className="item-border"
|
809
|
+
style={{
|
810
|
+
backgroundColor: this.props.getBorderColor(
|
811
|
+
option.value,
|
812
|
+
),
|
813
|
+
}}
|
814
|
+
>
|
815
|
+
</div>
|
816
|
+
}
|
817
|
+
<span
|
818
|
+
style={
|
819
|
+
(this.props.getBackgroundColor && option.value)
|
820
|
+
? {
|
821
|
+
backgroundColor:
|
822
|
+
this.props.getBackgroundColor(option.value),
|
823
|
+
color:
|
824
|
+
getTextColor(this.props.getBackgroundColor(
|
825
|
+
option.value,
|
826
|
+
),
|
827
|
+
),
|
828
|
+
}
|
829
|
+
: undefined
|
830
|
+
}
|
831
|
+
className={
|
832
|
+
'suggestion-item--bgcolor'
|
833
|
+
+ (selectedItem ? ' suggestion-item--disabled' : '')
|
834
|
+
}
|
835
|
+
>
|
836
|
+
{this.props.optionTemplate
|
837
|
+
? this.props.optionTemplate(option.value)
|
838
|
+
: this.props.getLabel(option.value)}
|
839
|
+
</span>
|
840
|
+
{option.children
|
841
|
+
&& <span className="suggestion-item__icon">
|
842
|
+
<Icon name="chevron-right-thin"></Icon>
|
843
|
+
</span>
|
844
|
+
}
|
845
|
+
</button>
|
846
|
+
</li>
|
847
|
+
);
|
848
|
+
})
|
849
|
+
}
|
850
|
+
</ul>
|
851
|
+
: null
|
852
|
+
: <ul className="suggestion-list suggestion-list--multi-select" ref={this.ref}>
|
853
|
+
{this.filteredItem(this.props.singleLevelSearch
|
854
|
+
? this.state.options : this.state.filterArr)}
|
855
|
+
</ul>
|
856
|
+
}
|
595
857
|
</div>
|
596
|
-
|
597
|
-
&& <div className='autocomplete__category-header'>
|
598
|
-
<div className="autocomplete__icon" onClick={() => {
|
599
|
-
this.backButtonValue();
|
600
|
-
this.backButton();
|
601
|
-
}}>
|
602
|
-
<Icon name="arrow-left" className="arrow-left"></Icon>
|
603
|
-
</div>
|
604
|
-
<div className='autocomplete__filter'>
|
605
|
-
<button
|
606
|
-
className={'autocomplete__category-title'}
|
607
|
-
value={this.state.buttonValue}>
|
608
|
-
{this.props.optionTemplate
|
609
|
-
? this.props.optionTemplate(this.state.buttonValue.value)
|
610
|
-
: this.props.getLabel(this.state.buttonValue.value)}
|
611
|
-
</button>
|
612
|
-
{this.props.selectBranchWithChildren && this.banchButton()}
|
613
|
-
</div>
|
614
|
-
</div>}
|
615
|
-
{this.state.loading
|
616
|
-
? <ul className="suggestion-list--loader"><Loader overlay={true}></Loader></ul>
|
617
|
-
: this.state.searchFieldValue === ''
|
618
|
-
? this.props.getOptions
|
619
|
-
? <ul className="suggestion-list suggestion-list--multi-select">
|
620
|
-
{this.state.options
|
621
|
-
.map((option, i: React.Key | undefined) => {
|
622
|
-
let selectedItem = this.state.value.some((obj) =>
|
623
|
-
this.props.getId(obj) === this.props.getLabel(option.value),
|
624
|
-
);
|
625
|
-
return (
|
626
|
-
<li key={i}
|
627
|
-
className={`suggestion-item suggestion-item--multi-select`}
|
628
|
-
onClick={(event) => {
|
629
|
-
event.preventDefault();
|
630
|
-
event.stopPropagation();
|
631
|
-
this.handleTree(event, option);
|
632
|
-
}}>
|
633
|
-
{(this.props.getBorderColor && !this.props.allowMultiple) && <div className="item-border" style={{backgroundColor: this.props.getBorderColor(option.value)}}></div>}
|
634
|
-
<span
|
635
|
-
style={(this.props.getBackgroundColor && option.value)
|
636
|
-
? {backgroundColor: this.props.getBackgroundColor(option.value),
|
637
|
-
color: getTextColor(this.props.getBackgroundColor(option.value))}
|
638
|
-
: undefined}
|
639
|
-
className={'suggestion-item--bgcolor'
|
640
|
-
+ (selectedItem ? ' suggestion-item--disabled' : '')}
|
641
|
-
>
|
642
|
-
{this.props.optionTemplate
|
643
|
-
? this.props.optionTemplate(option.value)
|
644
|
-
: this.props.getLabel(option.value)}
|
645
|
-
</span>
|
646
|
-
{option.children && <span className="suggestion-item__icon">
|
647
|
-
<Icon name="chevron-right-thin"></Icon>
|
648
|
-
</span>}
|
649
|
-
</li>
|
650
|
-
);
|
651
|
-
})}</ul> : null
|
652
|
-
: <ul className="suggestion-list suggestion-list--multi-select">
|
653
|
-
{this.filteredItem(this.props.singleLevelSearch
|
654
|
-
? this.state.options : this.state.filterArr)}
|
655
|
-
{/* {this.state.provera
|
656
|
-
&& <li className="suggestion-item--nothing-found">Nothing fonud</li>} */}
|
657
|
-
</ul>
|
658
|
-
}
|
659
|
-
</div>}
|
858
|
+
}
|
660
859
|
</div>
|
661
860
|
</InputWrapper>
|
662
861
|
);
|
663
862
|
}
|
664
863
|
}
|
864
|
+
|
865
|
+
const getButtonList = (menuRef: HTMLUListElement | undefined): Array<HTMLButtonElement> => {
|
866
|
+
let list = Array.from(menuRef?.querySelectorAll(':scope > li') ?? []);
|
867
|
+
let buttons: Array<HTMLButtonElement> = [];
|
868
|
+
|
869
|
+
if (list != null) {
|
870
|
+
[...list].filter((item) => {
|
871
|
+
if (item.querySelectorAll('.suggestion-item--btn').length > 0) {
|
872
|
+
buttons.push(item.querySelector('.suggestion-item--btn') as HTMLButtonElement);
|
873
|
+
}
|
874
|
+
});
|
875
|
+
}
|
876
|
+
|
877
|
+
return buttons;
|
878
|
+
};
|
879
|
+
|
880
|
+
const keyboardNavigation = (e?: KeyboardEvent, menuRef?: HTMLUListElement, ref?: () => void) => {
|
881
|
+
let buttons = getButtonList(menuRef);
|
882
|
+
const currentElement = document.activeElement;
|
883
|
+
const currentIndex = Array.prototype.indexOf.call(buttons, currentElement);
|
884
|
+
|
885
|
+
if (document.activeElement != null && buttons.includes(document.activeElement as HTMLButtonElement)) {
|
886
|
+
if (e?.key === 'ArrowDown') {
|
887
|
+
nextElement(buttons, currentIndex, e);
|
888
|
+
} else if (e?.key === 'ArrowUp') {
|
889
|
+
prevElement(buttons, currentIndex, e, ref);
|
890
|
+
}
|
891
|
+
}
|
892
|
+
};
|
893
|
+
|
894
|
+
const nextElement = (
|
895
|
+
buttons: Array<HTMLButtonElement>,
|
896
|
+
currentIndex: number,
|
897
|
+
e: KeyboardEvent,
|
898
|
+
) => {
|
899
|
+
e.preventDefault();
|
900
|
+
e.stopPropagation();
|
901
|
+
|
902
|
+
if (buttons[currentIndex + 1]) {
|
903
|
+
buttons[currentIndex + 1].focus();
|
904
|
+
} else {
|
905
|
+
buttons[0].focus();
|
906
|
+
}
|
907
|
+
};
|
908
|
+
|
909
|
+
const prevElement = (
|
910
|
+
buttons: Array<HTMLButtonElement>,
|
911
|
+
currentIndex: number,
|
912
|
+
e: KeyboardEvent,
|
913
|
+
ref: (() => void) | undefined,
|
914
|
+
) => {
|
915
|
+
e.preventDefault();
|
916
|
+
e.stopPropagation();
|
917
|
+
|
918
|
+
if (buttons[currentIndex - 1]) {
|
919
|
+
buttons[currentIndex - 1].focus();
|
920
|
+
} else if (currentIndex === 0) {
|
921
|
+
if (ref) {
|
922
|
+
ref();
|
923
|
+
}
|
924
|
+
} else {
|
925
|
+
buttons[buttons.length - 1].focus();
|
926
|
+
}
|
927
|
+
};
|