inviton-powerduck 0.0.225 → 0.0.227
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
/*! Ladda http://lab.hakim.se/ladda MIT licensed Copyright (C) 2016 Hakim El Hattab, http://hakim.se ....Lightweight adaptation for Inviton API needs*/
|
|
2
|
-
.ladda-button {
|
|
3
|
-
position: relative;
|
|
4
|
-
}
|
|
5
|
-
.ladda-button .ladda-spinner {
|
|
6
|
-
position: absolute;
|
|
7
|
-
z-index: 2;
|
|
8
|
-
display: inline-block;
|
|
9
|
-
width: 32px;
|
|
10
|
-
height: 32px;
|
|
11
|
-
top: 50%;
|
|
12
|
-
margin-top: 0;
|
|
13
|
-
opacity: 0;
|
|
14
|
-
pointer-events: none;
|
|
15
|
-
}
|
|
16
|
-
.ladda-button .ladda-label {
|
|
17
|
-
position: relative;
|
|
18
|
-
z-index: 3;
|
|
19
|
-
}
|
|
20
|
-
.ladda-button .ladda-progress {
|
|
21
|
-
position: absolute;
|
|
22
|
-
width: 0;
|
|
23
|
-
height: 100%;
|
|
24
|
-
left: 0;
|
|
25
|
-
top: 0;
|
|
26
|
-
background: rgba(0, 0, 0, 0.2);
|
|
27
|
-
visibility: hidden;
|
|
28
|
-
opacity: 0;
|
|
29
|
-
-webkit-transition: 0.1s linear all !important;
|
|
30
|
-
-moz-transition: 0.1s linear all !important;
|
|
31
|
-
-ms-transition: 0.1s linear all !important;
|
|
32
|
-
-o-transition: 0.1s linear all !important;
|
|
33
|
-
transition: 0.1s linear all !important;
|
|
34
|
-
}
|
|
35
|
-
.ladda-button[data-loading] .ladda-progress {
|
|
36
|
-
opacity: 1;
|
|
37
|
-
visibility: visible;
|
|
38
|
-
}
|
|
39
|
-
.ladda-button,
|
|
40
|
-
.ladda-button .ladda-label,
|
|
41
|
-
.ladda-button .ladda-spinner {
|
|
42
|
-
-webkit-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;
|
|
43
|
-
-moz-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;
|
|
44
|
-
-ms-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;
|
|
45
|
-
-o-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;
|
|
46
|
-
transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;
|
|
47
|
-
}
|
|
48
|
-
.ladda-button[data-style="zoom-in"],
|
|
49
|
-
.ladda-button[data-style="zoom-in"] .ladda-label,
|
|
50
|
-
.ladda-button[data-style="zoom-in"] .ladda-spinner {
|
|
51
|
-
-webkit-transition: 0.3s ease all !important;
|
|
52
|
-
-moz-transition: 0.3s ease all !important;
|
|
53
|
-
-ms-transition: 0.3s ease all !important;
|
|
54
|
-
-o-transition: 0.3s ease all !important;
|
|
55
|
-
transition: 0.3s ease all !important;
|
|
56
|
-
}
|
|
57
|
-
.ladda-button[data-style="zoom-in"] {
|
|
58
|
-
overflow: hidden;
|
|
59
|
-
}
|
|
60
|
-
.ladda-button[data-style="zoom-in"] .ladda-spinner {
|
|
61
|
-
left: 50%;
|
|
62
|
-
margin-left: -16px;
|
|
63
|
-
-webkit-transform: scale(0.2);
|
|
64
|
-
-moz-transform: scale(0.2);
|
|
65
|
-
-ms-transform: scale(0.2);
|
|
66
|
-
-o-transform: scale(0.2);
|
|
67
|
-
transform: scale(0.2);
|
|
68
|
-
}
|
|
69
|
-
.ladda-button[data-style="zoom-in"] .ladda-label {
|
|
70
|
-
position: relative;
|
|
71
|
-
display: inline-block;
|
|
72
|
-
}
|
|
73
|
-
.ladda-button[data-style="zoom-in"][data-loading] .ladda-label {
|
|
74
|
-
opacity: 0;
|
|
75
|
-
-webkit-transform: scale(2.2);
|
|
76
|
-
-moz-transform: scale(2.2);
|
|
77
|
-
-ms-transform: scale(2.2);
|
|
78
|
-
-o-transform: scale(2.2);
|
|
79
|
-
transform: scale(2.2);
|
|
80
|
-
}
|
|
81
|
-
.ladda-button[data-style="zoom-in"][data-loading] .ladda-spinner {
|
|
82
|
-
opacity: 1;
|
|
83
|
-
margin-left: 0;
|
|
84
|
-
-webkit-transform: none;
|
|
85
|
-
-moz-transform: none;
|
|
86
|
-
-ms-transform: none;
|
|
87
|
-
-o-transform: none;
|
|
88
|
-
transform: none;
|
|
89
|
-
}
|
|
1
|
+
/*! Ladda http://lab.hakim.se/ladda MIT licensed Copyright (C) 2016 Hakim El Hattab, http://hakim.se ....Lightweight adaptation for Inviton API needs*/
|
|
2
|
+
.ladda-button {
|
|
3
|
+
position: relative;
|
|
4
|
+
}
|
|
5
|
+
.ladda-button .ladda-spinner {
|
|
6
|
+
position: absolute;
|
|
7
|
+
z-index: 2;
|
|
8
|
+
display: inline-block;
|
|
9
|
+
width: 32px;
|
|
10
|
+
height: 32px;
|
|
11
|
+
top: 50%;
|
|
12
|
+
margin-top: 0;
|
|
13
|
+
opacity: 0;
|
|
14
|
+
pointer-events: none;
|
|
15
|
+
}
|
|
16
|
+
.ladda-button .ladda-label {
|
|
17
|
+
position: relative;
|
|
18
|
+
z-index: 3;
|
|
19
|
+
}
|
|
20
|
+
.ladda-button .ladda-progress {
|
|
21
|
+
position: absolute;
|
|
22
|
+
width: 0;
|
|
23
|
+
height: 100%;
|
|
24
|
+
left: 0;
|
|
25
|
+
top: 0;
|
|
26
|
+
background: rgba(0, 0, 0, 0.2);
|
|
27
|
+
visibility: hidden;
|
|
28
|
+
opacity: 0;
|
|
29
|
+
-webkit-transition: 0.1s linear all !important;
|
|
30
|
+
-moz-transition: 0.1s linear all !important;
|
|
31
|
+
-ms-transition: 0.1s linear all !important;
|
|
32
|
+
-o-transition: 0.1s linear all !important;
|
|
33
|
+
transition: 0.1s linear all !important;
|
|
34
|
+
}
|
|
35
|
+
.ladda-button[data-loading] .ladda-progress {
|
|
36
|
+
opacity: 1;
|
|
37
|
+
visibility: visible;
|
|
38
|
+
}
|
|
39
|
+
.ladda-button,
|
|
40
|
+
.ladda-button .ladda-label,
|
|
41
|
+
.ladda-button .ladda-spinner {
|
|
42
|
+
-webkit-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;
|
|
43
|
+
-moz-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;
|
|
44
|
+
-ms-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;
|
|
45
|
+
-o-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;
|
|
46
|
+
transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;
|
|
47
|
+
}
|
|
48
|
+
.ladda-button[data-style="zoom-in"],
|
|
49
|
+
.ladda-button[data-style="zoom-in"] .ladda-label,
|
|
50
|
+
.ladda-button[data-style="zoom-in"] .ladda-spinner {
|
|
51
|
+
-webkit-transition: 0.3s ease all !important;
|
|
52
|
+
-moz-transition: 0.3s ease all !important;
|
|
53
|
+
-ms-transition: 0.3s ease all !important;
|
|
54
|
+
-o-transition: 0.3s ease all !important;
|
|
55
|
+
transition: 0.3s ease all !important;
|
|
56
|
+
}
|
|
57
|
+
.ladda-button[data-style="zoom-in"] {
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
}
|
|
60
|
+
.ladda-button[data-style="zoom-in"] .ladda-spinner {
|
|
61
|
+
left: 50%;
|
|
62
|
+
margin-left: -16px;
|
|
63
|
+
-webkit-transform: scale(0.2);
|
|
64
|
+
-moz-transform: scale(0.2);
|
|
65
|
+
-ms-transform: scale(0.2);
|
|
66
|
+
-o-transform: scale(0.2);
|
|
67
|
+
transform: scale(0.2);
|
|
68
|
+
}
|
|
69
|
+
.ladda-button[data-style="zoom-in"] .ladda-label {
|
|
70
|
+
position: relative;
|
|
71
|
+
display: inline-block;
|
|
72
|
+
}
|
|
73
|
+
.ladda-button[data-style="zoom-in"][data-loading] .ladda-label {
|
|
74
|
+
opacity: 0;
|
|
75
|
+
-webkit-transform: scale(2.2);
|
|
76
|
+
-moz-transform: scale(2.2);
|
|
77
|
+
-ms-transform: scale(2.2);
|
|
78
|
+
-o-transform: scale(2.2);
|
|
79
|
+
transform: scale(2.2);
|
|
80
|
+
}
|
|
81
|
+
.ladda-button[data-style="zoom-in"][data-loading] .ladda-spinner {
|
|
82
|
+
opacity: 1;
|
|
83
|
+
margin-left: 0;
|
|
84
|
+
-webkit-transform: none;
|
|
85
|
+
-moz-transform: none;
|
|
86
|
+
-ms-transform: none;
|
|
87
|
+
-o-transform: none;
|
|
88
|
+
transform: none;
|
|
89
|
+
}
|
package/common/utils/cookie.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from '@js-temporal/polyfill';
|
|
2
|
-
import { DomainHelper } from './domain-helper';
|
|
3
2
|
import { globalState } from '../../app/global-state';
|
|
3
|
+
import { DomainHelper } from './domain-helper';
|
|
4
4
|
|
|
5
5
|
// Cookie manipulation abstraction
|
|
6
6
|
export class CookieProvider {
|
|
@@ -16,9 +16,9 @@ export class CookieProvider {
|
|
|
16
16
|
value: string,
|
|
17
17
|
expiration?: Temporal.PlainDateTime,
|
|
18
18
|
) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
if (!globalState.windowExists) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
22
|
|
|
23
23
|
let domain = DomainHelper.getCookieDomain();
|
|
24
24
|
if (domain.length > 0) {
|
|
@@ -27,7 +27,9 @@ export class CookieProvider {
|
|
|
27
27
|
|
|
28
28
|
let realExpiration;
|
|
29
29
|
if (expiration != null) {
|
|
30
|
-
|
|
30
|
+
const instant = expiration.toZonedDateTime('UTC').toInstant();
|
|
31
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
32
|
+
realExpiration = new Date(instant.epochMilliseconds).toUTCString();
|
|
31
33
|
} else {
|
|
32
34
|
realExpiration = '';
|
|
33
35
|
}
|
|
@@ -41,9 +43,9 @@ export class CookieProvider {
|
|
|
41
43
|
* @param name Name of the cookie
|
|
42
44
|
*/
|
|
43
45
|
static read(name: string): string {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
if (!globalState.windowExists) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
47
49
|
|
|
48
50
|
name = `${name}=`;
|
|
49
51
|
const ca = document.cookie.split(';');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.
|
|
1
|
+
.smart-dropdown-wrapper {
|
|
2
2
|
/* ... [Previous variables and base styles] ... */
|
|
3
3
|
--bs-blue: #0d6efd;
|
|
4
4
|
--bs-btn-bg: #0d6efd;
|
|
@@ -237,6 +237,15 @@
|
|
|
237
237
|
min-height: 150px;
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
.list-header {
|
|
241
|
+
padding: 12px 16px;
|
|
242
|
+
font-size: 0.85rem;
|
|
243
|
+
color: #6c757d;
|
|
244
|
+
background: #f8f9fa;
|
|
245
|
+
border-bottom: 1px solid #e9ecef;
|
|
246
|
+
font-style: italic;
|
|
247
|
+
}
|
|
248
|
+
|
|
240
249
|
.list-group-label {
|
|
241
250
|
padding: 8px 16px;
|
|
242
251
|
font-size: 0.75rem;
|
|
@@ -257,7 +266,10 @@
|
|
|
257
266
|
}
|
|
258
267
|
|
|
259
268
|
.list-item-custom-container {
|
|
269
|
+
padding: 8px 16px;
|
|
260
270
|
cursor: pointer;
|
|
271
|
+
display: flex;
|
|
272
|
+
align-items: center;
|
|
261
273
|
border-bottom: 1px solid #f8f9fa;
|
|
262
274
|
transition: background-color 0.15s;
|
|
263
275
|
|
|
@@ -376,7 +388,7 @@
|
|
|
376
388
|
|
|
377
389
|
/* When in 'input' mode on Desktop: Hide internal search header and footer */
|
|
378
390
|
@media (min-width: 769px) {
|
|
379
|
-
|
|
391
|
+
&.mode-input {
|
|
380
392
|
.internal-search-header {
|
|
381
393
|
display: none !important;
|
|
382
394
|
}
|
|
@@ -407,7 +419,7 @@
|
|
|
407
419
|
|
|
408
420
|
/* On Mobile, always show the internal search/footer even if in 'input' mode */
|
|
409
421
|
/* because the external input is behind the modal */
|
|
410
|
-
|
|
422
|
+
&.mode-input {
|
|
411
423
|
.internal-search-header {
|
|
412
424
|
display: flex !important;
|
|
413
425
|
}
|
|
@@ -49,6 +49,21 @@ export interface SmartDropdownSection {
|
|
|
49
49
|
items: SmartDropdownItem[];
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** Callback to change selection state of an item */
|
|
53
|
+
export type SelectionChangedCallback = (forceSelected?: boolean) => void;
|
|
54
|
+
|
|
55
|
+
/** Arguments passed to customListItemRender */
|
|
56
|
+
export interface CustomListItemRenderArgs {
|
|
57
|
+
/** The item being rendered */
|
|
58
|
+
item: SmartDropdownItem;
|
|
59
|
+
/** Whether the item is currently selected */
|
|
60
|
+
isSelected: boolean;
|
|
61
|
+
/** Callback to change selection state. Call with true to select, false to deselect, or undefined to toggle */
|
|
62
|
+
selectionChanged: SelectionChangedCallback;
|
|
63
|
+
/** Renders the item using the default SmartDropdown renderer */
|
|
64
|
+
baseRender: () => VNode;
|
|
65
|
+
}
|
|
66
|
+
|
|
52
67
|
interface SmartDropdownArgs extends FormItemWrapperArgs {
|
|
53
68
|
categories: SmartDropdownCategoryItem[];
|
|
54
69
|
customSections: SmartDropdownSection[];
|
|
@@ -60,9 +75,16 @@ interface SmartDropdownArgs extends FormItemWrapperArgs {
|
|
|
60
75
|
buttonLayout: 'footer' | 'inline';
|
|
61
76
|
searchMode: 'dropdown' | 'input';
|
|
62
77
|
customTriggerScope?: 'mobile' | 'desktop' | 'all';
|
|
63
|
-
|
|
78
|
+
/** Custom renderer for list items. Receives args object with item, isSelected, selectionChanged, and baseRender */
|
|
79
|
+
customListItemRender?: (args: CustomListItemRenderArgs) => VNode;
|
|
64
80
|
customTriggerRender?: () => VNode;
|
|
65
81
|
changed: (e: SmartDropdownItem[]) => void;
|
|
82
|
+
/** Called when an item is clicked. Return true to prevent default selection behavior. */
|
|
83
|
+
onItemClick?: (item: SmartDropdownItem) => boolean;
|
|
84
|
+
/** Delay in ms before showing the loading indicator. Default 650ms. */
|
|
85
|
+
loadingIndicatorDelay?: number;
|
|
86
|
+
/** Optional header text shown at the top of the list to guide users. */
|
|
87
|
+
listHeader?: string;
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
@Component
|
|
@@ -99,16 +121,21 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
|
|
|
99
121
|
@Prop({ type: String, default: 'footer' }) readonly buttonLayout!: 'footer' | 'inline';
|
|
100
122
|
@Prop({ type: String, default: 'dropdown' }) readonly searchMode!: 'dropdown' | 'input';
|
|
101
123
|
@Prop({ type: String, default: 'all' }) readonly customTriggerScope!: 'mobile' | 'desktop' | 'all';
|
|
102
|
-
@Prop({ type: Function }) readonly
|
|
124
|
+
@Prop({ type: Function }) readonly customListItemRender?: (args: CustomListItemRenderArgs) => VNode;
|
|
103
125
|
@Prop({ type: Function }) readonly customTriggerRender?: () => VNode;
|
|
104
126
|
@Prop() readonly changed: (e: SmartDropdownItem[]) => void;
|
|
127
|
+
@Prop({ type: Function }) readonly onItemClick?: (item: SmartDropdownItem) => boolean;
|
|
128
|
+
@Prop({ type: Number, default: 650 }) readonly loadingIndicatorDelay!: number;
|
|
129
|
+
@Prop({ type: String }) readonly listHeader?: string;
|
|
105
130
|
|
|
106
131
|
// --- State ---
|
|
107
132
|
isOpen = false;
|
|
108
133
|
searchQuery = '';
|
|
109
134
|
searchResults: SmartDropdownSearchResultItem[] = [];
|
|
110
135
|
isLoading = false;
|
|
136
|
+
isSearchPending = false;
|
|
111
137
|
debounceTimer: number | null = null;
|
|
138
|
+
loadingIndicatorTimer: number | null = null;
|
|
112
139
|
focusedIndex = -1;
|
|
113
140
|
triggerInputValue = '';
|
|
114
141
|
|
|
@@ -396,39 +423,69 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
|
|
|
396
423
|
clearTimeout(this.debounceTimer);
|
|
397
424
|
}
|
|
398
425
|
|
|
426
|
+
if (this.loadingIndicatorTimer) {
|
|
427
|
+
clearTimeout(this.loadingIndicatorTimer);
|
|
428
|
+
this.loadingIndicatorTimer = null;
|
|
429
|
+
}
|
|
430
|
+
|
|
399
431
|
if (val.trim().length === 0) {
|
|
400
432
|
this.isLoading = false;
|
|
433
|
+
this.isSearchPending = false;
|
|
401
434
|
this.searchResults = [];
|
|
402
435
|
return;
|
|
403
436
|
}
|
|
404
437
|
|
|
405
|
-
this.
|
|
438
|
+
this.isSearchPending = true;
|
|
406
439
|
this.debounceTimer = window.setTimeout(async () => {
|
|
440
|
+
// Start timer to show loading indicator after delay
|
|
441
|
+
this.loadingIndicatorTimer = window.setTimeout(() => {
|
|
442
|
+
this.isLoading = true;
|
|
443
|
+
}, this.loadingIndicatorDelay);
|
|
407
444
|
try {
|
|
408
445
|
this.searchResults = await this.searchData(val);
|
|
409
446
|
} catch (err) {
|
|
410
447
|
this.searchResults = [];
|
|
411
448
|
} finally {
|
|
449
|
+
if (this.loadingIndicatorTimer) {
|
|
450
|
+
clearTimeout(this.loadingIndicatorTimer);
|
|
451
|
+
this.loadingIndicatorTimer = null;
|
|
452
|
+
}
|
|
453
|
+
|
|
412
454
|
this.isLoading = false;
|
|
455
|
+
this.isSearchPending = false;
|
|
413
456
|
}
|
|
414
457
|
}, 800);
|
|
415
458
|
}
|
|
416
459
|
|
|
417
|
-
handleItemClick(item: SmartDropdownItem) {
|
|
418
|
-
|
|
460
|
+
handleItemClick(item: SmartDropdownItem, forceSelected?: boolean) {
|
|
461
|
+
// Allow parent to intercept and handle the click
|
|
462
|
+
if (this.onItemClick?.(item)) {
|
|
463
|
+
this.closeDropdown();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const isCurrentlySelected = this.isSelected(item);
|
|
468
|
+
// Use forceSelected if provided, otherwise toggle based on current state
|
|
469
|
+
const shouldBeSelected = forceSelected !== undefined ? forceSelected : !isCurrentlySelected;
|
|
470
|
+
|
|
419
471
|
let newSelection: SmartDropdownItem[] = [];
|
|
420
472
|
|
|
421
473
|
if (this.multiselect) {
|
|
422
|
-
if (
|
|
423
|
-
|
|
424
|
-
} else {
|
|
474
|
+
if (shouldBeSelected && !isCurrentlySelected) {
|
|
475
|
+
// Add item
|
|
425
476
|
newSelection = [
|
|
426
477
|
...this.value,
|
|
427
478
|
item,
|
|
428
479
|
];
|
|
480
|
+
} else if (!shouldBeSelected && isCurrentlySelected) {
|
|
481
|
+
// Remove item
|
|
482
|
+
newSelection = this.value.filter(i => i.id !== item.id);
|
|
483
|
+
} else {
|
|
484
|
+
// No change needed
|
|
485
|
+
newSelection = [...this.value];
|
|
429
486
|
}
|
|
430
487
|
} else {
|
|
431
|
-
newSelection = [item];
|
|
488
|
+
newSelection = shouldBeSelected ? [item] : [];
|
|
432
489
|
this.closeDropdown();
|
|
433
490
|
}
|
|
434
491
|
|
|
@@ -546,6 +603,11 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
|
|
|
546
603
|
// A. Search Results
|
|
547
604
|
if (this.isSearchActive) {
|
|
548
605
|
if (this.searchResults.length === 0) {
|
|
606
|
+
// Show loading if search is pending or in progress, otherwise show no results
|
|
607
|
+
if (this.isSearchPending) {
|
|
608
|
+
return <div class="loading-state" role="status">{SmartDropdownResources.loadingTextLong}</div>;
|
|
609
|
+
}
|
|
610
|
+
|
|
549
611
|
return <div class="empty-state" role="status">{SmartDropdownResources.noResultsSearch}</div>;
|
|
550
612
|
}
|
|
551
613
|
|
|
@@ -567,6 +629,11 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
|
|
|
567
629
|
|
|
568
630
|
return (
|
|
569
631
|
<div class="list-wrapper">
|
|
632
|
+
{/* 0. List Header */}
|
|
633
|
+
{this.listHeader && (
|
|
634
|
+
<div class="list-header" role="note">{this.listHeader}</div>
|
|
635
|
+
)}
|
|
636
|
+
|
|
570
637
|
{/* 1. Pinned Selected */}
|
|
571
638
|
{hasPinned && (
|
|
572
639
|
<div class="list-group section-pinned" role="group" aria-label={SmartDropdownResources.selectedTitle}>
|
|
@@ -600,17 +667,52 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
|
|
|
600
667
|
);
|
|
601
668
|
}
|
|
602
669
|
|
|
670
|
+
/** Renders the default list item content (without wrapper) */
|
|
671
|
+
renderDefaultListItemContent(item: SmartDropdownItem, isSelected: boolean): VNode {
|
|
672
|
+
const searchResult = item as SmartDropdownSearchResultItem;
|
|
673
|
+
const hasSubtitle = !!searchResult.subtitle;
|
|
674
|
+
const hasImage = !!searchResult.imageUrl;
|
|
675
|
+
const showCheckbox = this.multiselect && !this.isSearchActive && !hasImage;
|
|
676
|
+
|
|
677
|
+
return (
|
|
678
|
+
<>
|
|
679
|
+
{hasImage
|
|
680
|
+
? (
|
|
681
|
+
<img src={searchResult.imageUrl} class="result-image" alt="" />
|
|
682
|
+
)
|
|
683
|
+
: (
|
|
684
|
+
showCheckbox && <div class={`checkbox-visual ${isSelected ? 'checked' : ''}`} aria-hidden="true"></div>
|
|
685
|
+
)}
|
|
686
|
+
|
|
687
|
+
<div class="item-content">
|
|
688
|
+
<span class="item-text">{item.text}</span>
|
|
689
|
+
{hasSubtitle && (
|
|
690
|
+
<span class="item-subtitle">{searchResult.subtitle}</span>
|
|
691
|
+
)}
|
|
692
|
+
</div>
|
|
693
|
+
</>
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
603
697
|
renderListItem(item: SmartDropdownItem, index: number) {
|
|
604
698
|
const isSelected = this.isSelected(item);
|
|
605
699
|
const isFocused = this.focusedIndex === index;
|
|
606
700
|
const uniqueId = `${this.uid}-option-${index}`;
|
|
607
701
|
|
|
702
|
+
// Create selectionChanged callback for this item
|
|
703
|
+
const selectionChanged: SelectionChangedCallback = (forceSelected?: boolean) => {
|
|
704
|
+
this.handleItemClick(item, forceSelected);
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
// Create baseRender function that returns the default rendering
|
|
708
|
+
const baseRender = (): VNode => this.renderDefaultListItemContent(item, isSelected);
|
|
709
|
+
|
|
608
710
|
// Wrapper properties for accessibility
|
|
609
711
|
const wrapperProps = {
|
|
610
712
|
'id': uniqueId,
|
|
611
713
|
'class': {
|
|
612
|
-
'list-item-custom-container': !!this.
|
|
613
|
-
'list-item': !this.
|
|
714
|
+
'list-item-custom-container': !!this.customListItemRender,
|
|
715
|
+
'list-item': !this.customListItemRender,
|
|
614
716
|
'selected': isSelected,
|
|
615
717
|
'focused': isFocused, // Visual focus state
|
|
616
718
|
'no-checkbox': !this.multiselect,
|
|
@@ -622,36 +724,18 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
|
|
|
622
724
|
};
|
|
623
725
|
|
|
624
726
|
// 1. Custom Renderer
|
|
625
|
-
if (this.
|
|
727
|
+
if (this.customListItemRender) {
|
|
626
728
|
return (
|
|
627
729
|
<div {...wrapperProps}>
|
|
628
|
-
{this.
|
|
730
|
+
{this.customListItemRender({ item, isSelected, selectionChanged, baseRender })}
|
|
629
731
|
</div>
|
|
630
732
|
);
|
|
631
733
|
}
|
|
632
734
|
|
|
633
735
|
// 2. Default Renderer
|
|
634
|
-
const searchResult = item as SmartDropdownSearchResultItem;
|
|
635
|
-
const hasSubtitle = !!searchResult.subtitle;
|
|
636
|
-
const hasImage = !!searchResult.imageUrl;
|
|
637
|
-
const showCheckbox = this.multiselect && !this.isSearchActive && !hasImage;
|
|
638
|
-
|
|
639
736
|
return (
|
|
640
737
|
<div {...wrapperProps}>
|
|
641
|
-
{
|
|
642
|
-
? (
|
|
643
|
-
<img src={searchResult.imageUrl} class="result-image" alt="" />
|
|
644
|
-
)
|
|
645
|
-
: (
|
|
646
|
-
showCheckbox && <div class={`checkbox-visual ${isSelected ? 'checked' : ''}`} aria-hidden="true"></div>
|
|
647
|
-
)}
|
|
648
|
-
|
|
649
|
-
<div class="item-content">
|
|
650
|
-
<span class="item-text">{item.text}</span>
|
|
651
|
-
{hasSubtitle && (
|
|
652
|
-
<span class="item-subtitle">{searchResult.subtitle}</span>
|
|
653
|
-
)}
|
|
654
|
-
</div>
|
|
738
|
+
{this.renderDefaultListItemContent(item, isSelected)}
|
|
655
739
|
</div>
|
|
656
740
|
);
|
|
657
741
|
}
|
|
@@ -662,7 +746,7 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
|
|
|
662
746
|
return (
|
|
663
747
|
<FormItemWrapper label={this.label} cssClass={this.cssClass} mandatory={this.mandatory} wrap={this.wrap} appendIcon={this.appendIcon} prependIcon={this.prependIcon} hint={this.hint} marginType={this.marginType} appendClicked={this.appendClicked} prependClicked={this.prependClicked} prependIconClicked={this.prependIconClicked} appendIconClicked={this.appendIconClicked} maxWidth={this.maxWidth} validationState={this.validationState} labelButtons={this.labelButtons} subtitle={this.subtitle} showClearValueButton={this.showClearValueButton}>
|
|
664
748
|
<div
|
|
665
|
-
class={`
|
|
749
|
+
class={`smart-dropdown-wrapper mode-${this.searchMode}`}
|
|
666
750
|
// Only the outer wrapper acts as combobox if not in direct input mode
|
|
667
751
|
role={this.searchMode !== 'input' ? 'combobox' : undefined}
|
|
668
752
|
aria-expanded={this.isOpen}
|
|
@@ -122,7 +122,7 @@ class CheckBoxComponent extends TsxComponent<CheckBoxArgs> implements CheckBoxAr
|
|
|
122
122
|
<label class="form-check-label">
|
|
123
123
|
<input class="form-check-input" type="checkbox" checked={this.value} disabled={this.disabled || undefined} onChange={e => this.raiseChangeEvent(e)} />
|
|
124
124
|
<span class="form-check-sign"></span>
|
|
125
|
-
{this.getCustomRenderedLabel
|
|
125
|
+
{this.getCustomRenderedLabel(h) ?? this.checkboxLabelHtml}
|
|
126
126
|
</label>
|
|
127
127
|
</div>
|
|
128
128
|
);
|
|
@@ -140,7 +140,7 @@ class CheckBoxComponent extends TsxComponent<CheckBoxArgs> implements CheckBoxAr
|
|
|
140
140
|
<div class="inv-cbrb-inner">
|
|
141
141
|
<input class="inv-chckb-clickable" type="checkbox" id={`iei-input-${this.uuid}`} checked={this.value} disabled={this.disabled || undefined} onChange={e => this.raiseChangeEvent(e)} />
|
|
142
142
|
<label class="inv-chckb-clickable form-check-label " for={`iei-input-${this.uuid}`}>
|
|
143
|
-
{this.getCustomRenderedLabel
|
|
143
|
+
{this.getCustomRenderedLabel(h) ?? <HtmlLiteral innerHTML={this.checkboxLabelHtml} />}
|
|
144
144
|
</label>
|
|
145
145
|
</div>
|
|
146
146
|
</div>
|
|
@@ -160,7 +160,7 @@ class CheckBoxComponent extends TsxComponent<CheckBoxArgs> implements CheckBoxAr
|
|
|
160
160
|
<div class="inv-cbrb-inner">
|
|
161
161
|
<input class="inv-chckb-clickable" type="checkbox" id={`iei-input-${this.uuid}`} checked={this.value} disabled={this.disabled || undefined} onChange={e => this.raiseChangeEvent(e)} />
|
|
162
162
|
<label class="inv-chckb-clickable form-check-label " for={`iei-input-${this.uuid}`}>
|
|
163
|
-
{this.getCustomRenderedLabel
|
|
163
|
+
{this.getCustomRenderedLabel(h) ?? <HtmlLiteral innerHTML={this.checkboxLabelHtml} />}
|
|
164
164
|
</label>
|
|
165
165
|
</div>
|
|
166
166
|
</div>
|