@nyaruka/temba-components 0.112.0 → 0.114.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.
- package/CHANGELOG.md +27 -2
- package/demo/index.html +1 -1
- package/dist/temba-components.js +794 -968
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/aliaseditor/AliasEditor.js.map +1 -1
- package/out-tsc/src/button/Button.js +6 -2
- package/out-tsc/src/button/Button.js.map +1 -1
- package/out-tsc/src/chat/Chat.js +29 -7
- package/out-tsc/src/chat/Chat.js.map +1 -1
- package/out-tsc/src/compose/Compose.js +16 -20
- package/out-tsc/src/compose/Compose.js.map +1 -1
- package/out-tsc/src/contacts/ContactChat.js +240 -114
- package/out-tsc/src/contacts/ContactChat.js.map +1 -1
- package/out-tsc/src/contacts/ContactFieldEditor.js.map +1 -1
- package/out-tsc/src/contacts/events.js.map +1 -1
- package/out-tsc/src/contacts/helpers.js +5 -1
- package/out-tsc/src/contacts/helpers.js.map +1 -1
- package/out-tsc/src/contactsearch/ContactSearch.js +1 -1
- package/out-tsc/src/contactsearch/ContactSearch.js.map +1 -1
- package/out-tsc/src/dropdown/Dropdown.js +121 -108
- package/out-tsc/src/dropdown/Dropdown.js.map +1 -1
- package/out-tsc/src/interfaces.js +2 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/list/ContentMenu.js +11 -8
- package/out-tsc/src/list/ContentMenu.js.map +1 -1
- package/out-tsc/src/list/RunList.js.map +1 -1
- package/out-tsc/src/list/TembaList.js +21 -14
- package/out-tsc/src/list/TembaList.js.map +1 -1
- package/out-tsc/src/list/TembaMenu.js +11 -12
- package/out-tsc/src/list/TembaMenu.js.map +1 -1
- package/out-tsc/src/list/TicketList.js +10 -0
- package/out-tsc/src/list/TicketList.js.map +1 -1
- package/out-tsc/src/omnibox/Omnibox.js +33 -90
- package/out-tsc/src/omnibox/Omnibox.js.map +1 -1
- package/out-tsc/src/options/Options.js +49 -47
- package/out-tsc/src/options/Options.js.map +1 -1
- package/out-tsc/src/select/PopupSelect.js +57 -0
- package/out-tsc/src/select/PopupSelect.js.map +1 -0
- package/out-tsc/src/select/Select.js +194 -144
- package/out-tsc/src/select/Select.js.map +1 -1
- package/out-tsc/src/select/UserSelect.js +67 -0
- package/out-tsc/src/select/UserSelect.js.map +1 -0
- package/out-tsc/src/store/Store.js +65 -14
- package/out-tsc/src/store/Store.js.map +1 -1
- package/out-tsc/src/tabpane/TabPane.js +82 -115
- package/out-tsc/src/tabpane/TabPane.js.map +1 -1
- package/out-tsc/src/textinput/TextInput.js +1 -0
- package/out-tsc/src/textinput/TextInput.js.map +1 -1
- package/out-tsc/src/user/TembaUser.js +24 -37
- package/out-tsc/src/user/TembaUser.js.map +1 -1
- package/out-tsc/src/utils/index.js +13 -6
- package/out-tsc/src/utils/index.js.map +1 -1
- package/out-tsc/temba-modules.js +4 -2
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-omnibox.test.js +43 -4
- package/out-tsc/test/temba-omnibox.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +121 -65
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +4 -0
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/compose/attachments-tab.png +0 -0
- package/screenshots/truth/compose/attachments-with-files-focused.png +0 -0
- package/screenshots/truth/compose/attachments-with-files.png +0 -0
- package/screenshots/truth/compose/intial-text.png +0 -0
- package/screenshots/truth/compose/no-counter.png +0 -0
- package/screenshots/truth/compose/wraps-text-and-spaces.png +0 -0
- package/screenshots/truth/compose/wraps-text-and-url.png +0 -0
- package/screenshots/truth/compose/wraps-text-no-spaces.png +0 -0
- package/screenshots/truth/contacts/chat-failure.png +0 -0
- package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
- package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
- package/screenshots/truth/content-menu/item-no-buttons.png +0 -0
- package/screenshots/truth/content-menu/items-and-buttons.png +0 -0
- package/screenshots/truth/omnibox/selected.png +0 -0
- package/screenshots/truth/select/enabled-multi-selection.png +0 -0
- package/screenshots/truth/select/endpoint-initial-value-updated.png +0 -0
- package/screenshots/truth/select/endpoint-initial-value.png +0 -0
- package/screenshots/truth/select/expressions.png +0 -0
- package/screenshots/truth/select/functions.png +0 -0
- package/screenshots/truth/select/initial-value.png +0 -0
- package/screenshots/truth/select/multi-with-endpoint.png +0 -0
- package/screenshots/truth/select/multiple-initial-values.png +0 -0
- package/screenshots/truth/select/selected-multi-test.png +0 -0
- package/screenshots/truth/select/static-initial-value.png +0 -0
- package/screenshots/truth/select/static-initial-via-selected.png +0 -0
- package/screenshots/truth/select/value-initial.png +0 -0
- package/src/aliaseditor/AliasEditor.ts +1 -1
- package/src/button/Button.ts +6 -2
- package/src/chat/Chat.ts +28 -6
- package/src/compose/Compose.ts +17 -22
- package/src/contacts/ContactChat.ts +260 -118
- package/src/contacts/ContactFieldEditor.ts +1 -1
- package/src/contacts/events.ts +1 -0
- package/src/contacts/helpers.ts +8 -1
- package/src/contactsearch/ContactSearch.ts +3 -3
- package/src/dropdown/Dropdown.ts +142 -103
- package/src/interfaces.ts +4 -1
- package/src/list/ContentMenu.ts +11 -9
- package/src/list/RunList.ts +3 -1
- package/src/list/TembaList.ts +24 -14
- package/src/list/TembaMenu.ts +14 -15
- package/src/list/TicketList.ts +11 -0
- package/src/omnibox/Omnibox.ts +34 -95
- package/src/options/Options.ts +57 -60
- package/src/select/PopupSelect.ts +53 -0
- package/src/select/Select.ts +182 -112
- package/src/select/UserSelect.ts +71 -0
- package/src/store/Store.ts +70 -21
- package/src/tabpane/TabPane.ts +91 -113
- package/src/textinput/TextInput.ts +1 -0
- package/src/user/TembaUser.ts +30 -41
- package/src/utils/index.ts +12 -8
- package/temba-modules.ts +4 -2
- package/test/temba-omnibox.test.ts +56 -4
- package/test/temba-select.test.ts +170 -56
- package/test/utils.test.ts +5 -0
- package/test-assets/select/omnibox.json +55 -0
- package/web-test-runner.config.mjs +16 -4
- package/out-tsc/src/contacts/ContactTickets.js +0 -462
- package/out-tsc/src/contacts/ContactTickets.js.map +0 -1
- package/out-tsc/test/temba-contact-tickets.test.js +0 -36
- package/out-tsc/test/temba-contact-tickets.test.js.map +0 -1
- package/src/contacts/ContactTickets.ts +0 -490
- package/test/temba-contact-tickets.test.ts +0 -52
package/src/select/Select.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
2
|
-
import { TemplateResult, html, css } from 'lit';
|
|
2
|
+
import { TemplateResult, html, css, CSSResult, CSSResultArray } from 'lit';
|
|
3
3
|
import { property } from 'lit/decorators.js';
|
|
4
4
|
import {
|
|
5
5
|
getUrl,
|
|
@@ -26,12 +26,20 @@ import { msg } from '@lit/localize';
|
|
|
26
26
|
|
|
27
27
|
const LOOK_AHEAD = 20;
|
|
28
28
|
|
|
29
|
-
export
|
|
29
|
+
export interface SelectOption {
|
|
30
|
+
name: string;
|
|
31
|
+
value?: string;
|
|
32
|
+
expression?: boolean;
|
|
33
|
+
selected?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class Select<T extends SelectOption> extends FormElement {
|
|
30
37
|
private hiddenInputs: HTMLInputElement[] = [];
|
|
31
38
|
|
|
32
|
-
static get styles() {
|
|
39
|
+
static get styles(): CSSResult | CSSResultArray {
|
|
33
40
|
return css`
|
|
34
41
|
:host {
|
|
42
|
+
--transition-speed: 0;
|
|
35
43
|
font-family: var(--font-family);
|
|
36
44
|
transition: all ease-in-out var(--transition-speed);
|
|
37
45
|
display: inline;
|
|
@@ -44,6 +52,7 @@ export class Select extends FormElement {
|
|
|
44
52
|
temba-options {
|
|
45
53
|
--temba-options-font-size: var(--temba-select-selected-font-size);
|
|
46
54
|
--icon-color: var(--color-text-dark);
|
|
55
|
+
--color-options-bg: #fff;
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
:host:focus {
|
|
@@ -370,6 +379,9 @@ export class Select extends FormElement {
|
|
|
370
379
|
@property({ type: Boolean })
|
|
371
380
|
fetching: boolean;
|
|
372
381
|
|
|
382
|
+
@property({ type: Boolean })
|
|
383
|
+
resolving: boolean;
|
|
384
|
+
|
|
373
385
|
@property({ type: Boolean })
|
|
374
386
|
searchable = false;
|
|
375
387
|
|
|
@@ -428,7 +440,7 @@ export class Select extends FormElement {
|
|
|
428
440
|
infoText = '';
|
|
429
441
|
|
|
430
442
|
@property({ type: Array })
|
|
431
|
-
values:
|
|
443
|
+
values: T[] = [];
|
|
432
444
|
|
|
433
445
|
@property({ type: Object })
|
|
434
446
|
selection: any;
|
|
@@ -438,10 +450,7 @@ export class Select extends FormElement {
|
|
|
438
450
|
option[this.nameKey || 'name'];
|
|
439
451
|
|
|
440
452
|
@property({ attribute: false })
|
|
441
|
-
isMatch: (option: any, q: string) => boolean =
|
|
442
|
-
const name = this.getName(option) || '';
|
|
443
|
-
return name.toLowerCase().indexOf(q) > -1;
|
|
444
|
-
};
|
|
453
|
+
isMatch: (option: any, q: string) => boolean = this.isMatchDefault;
|
|
445
454
|
|
|
446
455
|
@property({ attribute: false })
|
|
447
456
|
getValue: (option: any) => string = (option: any) =>
|
|
@@ -481,7 +490,7 @@ export class Select extends FormElement {
|
|
|
481
490
|
getOptions: (response: WebResponse) => any[] = this.getOptionsDefault;
|
|
482
491
|
|
|
483
492
|
@property({ attribute: false })
|
|
484
|
-
prepareOptions: (options: any[]) => any[] =
|
|
493
|
+
prepareOptions: (options: any[]) => any[] = this.prepareOptionsDefault;
|
|
485
494
|
|
|
486
495
|
@property({ attribute: false })
|
|
487
496
|
isComplete: (newestOptions: any[], response: WebResponse) => boolean =
|
|
@@ -500,18 +509,32 @@ export class Select extends FormElement {
|
|
|
500
509
|
|
|
501
510
|
private lastQuery: number;
|
|
502
511
|
|
|
503
|
-
// private cancelToken: CancelTokenSource;
|
|
504
512
|
private complete: boolean;
|
|
505
513
|
private page: number;
|
|
506
514
|
private next: string = null;
|
|
507
515
|
private query: string;
|
|
508
516
|
|
|
509
|
-
private removingSelection: boolean;
|
|
510
|
-
|
|
511
517
|
private lruCache = lru(20, 60000);
|
|
512
518
|
|
|
519
|
+
constructor() {
|
|
520
|
+
super();
|
|
521
|
+
this.renderOptionDefault = this.renderOptionDefault.bind(this);
|
|
522
|
+
this.renderSelectedItemDefault = this.renderSelectedItemDefault.bind(this);
|
|
523
|
+
this.prepareOptionsDefault = this.prepareOptionsDefault.bind(this);
|
|
524
|
+
this.isMatchDefault = this.isMatchDefault.bind(this);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
public prepareOptionsDefault(options: T[]): T[] {
|
|
528
|
+
return options;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
public isMatchDefault(option: T, q: string) {
|
|
532
|
+
const name = this.getName(option) || '';
|
|
533
|
+
return name.toLowerCase().indexOf(q) > -1;
|
|
534
|
+
}
|
|
535
|
+
|
|
513
536
|
public handleSlotChange() {
|
|
514
|
-
if (this.staticOptions.length === 0) {
|
|
537
|
+
if (this.staticOptions && this.staticOptions.length === 0) {
|
|
515
538
|
for (const child of this.children) {
|
|
516
539
|
if (child.tagName === 'TEMBA-OPTION') {
|
|
517
540
|
const option: any = {};
|
|
@@ -520,12 +543,26 @@ export class Select extends FormElement {
|
|
|
520
543
|
}
|
|
521
544
|
|
|
522
545
|
if (option) {
|
|
546
|
+
let selected = false;
|
|
547
|
+
|
|
548
|
+
// if the option is marked as selected then accept it
|
|
549
|
+
if (option['selected'] !== undefined) {
|
|
550
|
+
delete option['selected'];
|
|
551
|
+
selected = true;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// the option value might also match the widget value
|
|
555
|
+
const selectValue = this.value || this.getAttribute('value');
|
|
556
|
+
if (selectValue) {
|
|
557
|
+
const optionValue = this.getValue(option);
|
|
558
|
+
if (optionValue == selectValue) {
|
|
559
|
+
selected = true;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
523
563
|
this.staticOptions.push(option);
|
|
524
|
-
if (
|
|
525
|
-
|
|
526
|
-
this.getValue(option) == this.value
|
|
527
|
-
) {
|
|
528
|
-
if (this.getAttribute('multi') !== null) {
|
|
564
|
+
if (selected) {
|
|
565
|
+
if (this.multi) {
|
|
529
566
|
this.addValue(option);
|
|
530
567
|
} else {
|
|
531
568
|
this.setValues([option]);
|
|
@@ -536,43 +573,49 @@ export class Select extends FormElement {
|
|
|
536
573
|
}
|
|
537
574
|
}
|
|
538
575
|
|
|
539
|
-
this.checkSelectedOption();
|
|
540
|
-
|
|
541
576
|
if (this.searchable && this.staticOptions.length === 0) {
|
|
542
577
|
this.quietMillis = 200;
|
|
543
578
|
}
|
|
544
579
|
}
|
|
545
580
|
|
|
546
581
|
private checkSelectedOption() {
|
|
547
|
-
if
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
582
|
+
// see if we need fetch to select an option
|
|
583
|
+
if (
|
|
584
|
+
this.value &&
|
|
585
|
+
this.values.length == 0 &&
|
|
586
|
+
this.staticOptions.length == 0 &&
|
|
587
|
+
this.endpoint
|
|
588
|
+
) {
|
|
589
|
+
const value = this.value;
|
|
590
|
+
this.resolving = true;
|
|
591
|
+
|
|
592
|
+
fetchResults(this.endpoint).then((results: any) => {
|
|
593
|
+
if (results && results.length > 0) {
|
|
594
|
+
if (value) {
|
|
595
|
+
// if we started with a value, see if we can find it in the results
|
|
596
|
+
const existing = results.find((option) => {
|
|
597
|
+
return this.getValue(option) === value;
|
|
598
|
+
});
|
|
599
|
+
if (existing) {
|
|
600
|
+
this.resolving = false;
|
|
601
|
+
this.fetching = false;
|
|
602
|
+
this.setValues([existing]);
|
|
603
|
+
return;
|
|
563
604
|
}
|
|
564
|
-
this.setValues([results[0]]);
|
|
565
605
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
606
|
+
|
|
607
|
+
this.setValues([results[0]]);
|
|
608
|
+
this.resolving = false;
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
} else if (this.staticOptions.length > 0) {
|
|
612
|
+
if (this.getAttribute('multi') !== null) {
|
|
613
|
+
this.addValue(this.staticOptions[0]);
|
|
614
|
+
} else {
|
|
615
|
+
if (this.getAttribute('value')) {
|
|
616
|
+
this.setSelectedValue(this.getAttribute('value'));
|
|
570
617
|
} else {
|
|
571
|
-
|
|
572
|
-
this.setSelectedValue(this.getAttribute('value'));
|
|
573
|
-
} else {
|
|
574
|
-
this.setValues([this.staticOptions[0]]);
|
|
575
|
-
}
|
|
618
|
+
this.setValues([this.staticOptions[0]]);
|
|
576
619
|
}
|
|
577
620
|
}
|
|
578
621
|
}
|
|
@@ -586,40 +629,37 @@ export class Select extends FormElement {
|
|
|
586
629
|
'slotchange',
|
|
587
630
|
this.handleSlotChange.bind(this)
|
|
588
631
|
);
|
|
589
|
-
|
|
590
|
-
this.checkSelectedOption();
|
|
591
632
|
}
|
|
592
633
|
|
|
593
|
-
public updated(
|
|
594
|
-
super.updated(
|
|
634
|
+
public updated(changes: Map<string, any>) {
|
|
635
|
+
super.updated(changes);
|
|
595
636
|
|
|
596
|
-
if (
|
|
637
|
+
if (changes.has('sorted')) {
|
|
597
638
|
this.sortFunction = this.sorted ? this.alphaSort : null;
|
|
598
639
|
}
|
|
599
640
|
|
|
600
|
-
if (
|
|
641
|
+
if (changes.has('value')) {
|
|
642
|
+
if (this.value && !this.values.length) {
|
|
643
|
+
this.setSelectedValue(this.value);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (changes.has('values')) {
|
|
601
648
|
this.updateInputs();
|
|
602
|
-
if (
|
|
603
|
-
this.multi ||
|
|
604
|
-
this.values.length === 1 ||
|
|
605
|
-
// fire change if being cleared
|
|
606
|
-
(this.values.length == 0 &&
|
|
607
|
-
changedProperties.get('values') &&
|
|
608
|
-
changedProperties.get('values').length > 0)
|
|
609
|
-
) {
|
|
649
|
+
if (this.hasChanges(changes.get('values'))) {
|
|
610
650
|
this.fireEvent('change');
|
|
611
651
|
}
|
|
612
652
|
}
|
|
613
653
|
|
|
614
654
|
// if our cache key changes, clear it out
|
|
615
|
-
if (
|
|
655
|
+
if (changes.has('cacheKey')) {
|
|
616
656
|
this.lruCache.clear();
|
|
617
657
|
}
|
|
618
658
|
|
|
619
659
|
if (
|
|
620
|
-
|
|
621
|
-
!
|
|
622
|
-
!
|
|
660
|
+
changes.has('input') &&
|
|
661
|
+
!changes.has('values') &&
|
|
662
|
+
!changes.has('options') &&
|
|
623
663
|
this.focused
|
|
624
664
|
) {
|
|
625
665
|
if (this.lastQuery) {
|
|
@@ -635,7 +675,7 @@ export class Select extends FormElement {
|
|
|
635
675
|
}, this.quietMillis);
|
|
636
676
|
}
|
|
637
677
|
|
|
638
|
-
if (this.endpoint &&
|
|
678
|
+
if (this.endpoint && changes.has('fetching')) {
|
|
639
679
|
if (!this.fetching && !this.isPastFetchThreshold()) {
|
|
640
680
|
this.fireCustomEvent(CustomEventType.FetchComplete);
|
|
641
681
|
}
|
|
@@ -643,8 +683,7 @@ export class Select extends FormElement {
|
|
|
643
683
|
|
|
644
684
|
// if our cursor changed, lets make sure our scrollbox is showing it
|
|
645
685
|
if (
|
|
646
|
-
(
|
|
647
|
-
changedProperties.has('visibleOptions')) &&
|
|
686
|
+
(changes.has('cursorIndex') || changes.has('visibleOptions')) &&
|
|
648
687
|
this.endpoint &&
|
|
649
688
|
!this.fetching
|
|
650
689
|
) {
|
|
@@ -657,11 +696,6 @@ export class Select extends FormElement {
|
|
|
657
696
|
}
|
|
658
697
|
}
|
|
659
698
|
|
|
660
|
-
// if they set an inital value, look through our static options for it
|
|
661
|
-
if (changedProperties.has('value') && this.value && !this.values.length) {
|
|
662
|
-
this.setSelectedValue(this.value);
|
|
663
|
-
}
|
|
664
|
-
|
|
665
699
|
// default to the first option if we don't have a placeholder
|
|
666
700
|
if (
|
|
667
701
|
this.values.length === 0 &&
|
|
@@ -672,6 +706,29 @@ export class Select extends FormElement {
|
|
|
672
706
|
}
|
|
673
707
|
}
|
|
674
708
|
|
|
709
|
+
private hasChanges(prev: T[]): boolean {
|
|
710
|
+
if (prev === undefined) {
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
let prevValues = undefined;
|
|
715
|
+
if (prev !== undefined) {
|
|
716
|
+
prevValues = (prev || [])
|
|
717
|
+
.map((option: T) => {
|
|
718
|
+
return this.getValue(option);
|
|
719
|
+
})
|
|
720
|
+
.join(',');
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const newValues = (this.values || [])
|
|
724
|
+
.map((option: T) => {
|
|
725
|
+
return this.getValue(option);
|
|
726
|
+
})
|
|
727
|
+
.join(',');
|
|
728
|
+
|
|
729
|
+
return prevValues !== newValues;
|
|
730
|
+
}
|
|
731
|
+
|
|
675
732
|
public setSelectedValue(value: string) {
|
|
676
733
|
if (this.staticOptions.length > 0) {
|
|
677
734
|
const existing = this.staticOptions.find((option) => {
|
|
@@ -681,6 +738,8 @@ export class Select extends FormElement {
|
|
|
681
738
|
if (existing) {
|
|
682
739
|
this.setValues([existing]);
|
|
683
740
|
}
|
|
741
|
+
} else {
|
|
742
|
+
this.checkSelectedOption();
|
|
684
743
|
}
|
|
685
744
|
}
|
|
686
745
|
|
|
@@ -742,6 +801,9 @@ export class Select extends FormElement {
|
|
|
742
801
|
}
|
|
743
802
|
|
|
744
803
|
public handleOptionSelection(event: CustomEvent) {
|
|
804
|
+
event.preventDefault();
|
|
805
|
+
event.stopPropagation();
|
|
806
|
+
|
|
745
807
|
const selected = event.detail.selected;
|
|
746
808
|
// check if we should post it
|
|
747
809
|
if (selected.post && this.endpoint) {
|
|
@@ -776,7 +838,7 @@ export class Select extends FormElement {
|
|
|
776
838
|
}
|
|
777
839
|
}
|
|
778
840
|
|
|
779
|
-
|
|
841
|
+
protected getNameInternal: (option: T) => string = (option: T) => {
|
|
780
842
|
return this.getName(option);
|
|
781
843
|
};
|
|
782
844
|
|
|
@@ -802,7 +864,9 @@ export class Select extends FormElement {
|
|
|
802
864
|
}
|
|
803
865
|
|
|
804
866
|
public open(): void {
|
|
805
|
-
|
|
867
|
+
(
|
|
868
|
+
this.shadowRoot.querySelector('.select-container') as HTMLDivElement
|
|
869
|
+
).click();
|
|
806
870
|
}
|
|
807
871
|
|
|
808
872
|
public isOpen(): boolean {
|
|
@@ -925,11 +989,6 @@ export class Select extends FormElement {
|
|
|
925
989
|
if (!this.fetching) {
|
|
926
990
|
this.fetching = true;
|
|
927
991
|
|
|
928
|
-
// make sure we cancel any previous request
|
|
929
|
-
// if (this.cancelToken) {
|
|
930
|
-
// this.cancelToken.cancel();
|
|
931
|
-
// }
|
|
932
|
-
|
|
933
992
|
const options: any = [...this.staticOptions];
|
|
934
993
|
const q = (query || '').trim().toLowerCase();
|
|
935
994
|
|
|
@@ -1055,9 +1114,6 @@ export class Select extends FormElement {
|
|
|
1055
1114
|
private handleFocus(): void {
|
|
1056
1115
|
if (!this.focused && this.visibleOptions.length === 0) {
|
|
1057
1116
|
this.focused = true;
|
|
1058
|
-
if (this.searchOnFocus && !this.removingSelection) {
|
|
1059
|
-
this.requestUpdate('input');
|
|
1060
|
-
}
|
|
1061
1117
|
}
|
|
1062
1118
|
}
|
|
1063
1119
|
|
|
@@ -1087,7 +1143,7 @@ export class Select extends FormElement {
|
|
|
1087
1143
|
|
|
1088
1144
|
if (this.multi) {
|
|
1089
1145
|
if (
|
|
1090
|
-
!this.values.find((option) => {
|
|
1146
|
+
!this.values.find((option: T) => {
|
|
1091
1147
|
return (
|
|
1092
1148
|
option.expression &&
|
|
1093
1149
|
option.value &&
|
|
@@ -1204,25 +1260,25 @@ export class Select extends FormElement {
|
|
|
1204
1260
|
}
|
|
1205
1261
|
|
|
1206
1262
|
private handleArrowClick(event: MouseEvent): void {
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
this.
|
|
1211
|
-
} else {
|
|
1212
|
-
this.handleContainerClick(event);
|
|
1263
|
+
if (this.isOpen()) {
|
|
1264
|
+
event.preventDefault();
|
|
1265
|
+
event.stopPropagation();
|
|
1266
|
+
this.blur();
|
|
1213
1267
|
}
|
|
1214
1268
|
}
|
|
1215
1269
|
|
|
1216
|
-
|
|
1270
|
+
public renderOptionDefault(option: T): TemplateResult {
|
|
1217
1271
|
if (!option) {
|
|
1218
1272
|
return null;
|
|
1219
1273
|
}
|
|
1220
1274
|
|
|
1275
|
+
// special case for icons on any option type
|
|
1276
|
+
const icon = (option as any).icon;
|
|
1221
1277
|
return html`
|
|
1222
1278
|
<div class="option-name" style="display:flex">
|
|
1223
|
-
${
|
|
1279
|
+
${icon
|
|
1224
1280
|
? html`<temba-icon
|
|
1225
|
-
name="${
|
|
1281
|
+
name="${icon}"
|
|
1226
1282
|
style="margin-right:0.5em;"
|
|
1227
1283
|
></temba-icon>`
|
|
1228
1284
|
: null}<span>${this.getName(option)}</span>
|
|
@@ -1230,6 +1286,11 @@ export class Select extends FormElement {
|
|
|
1230
1286
|
`;
|
|
1231
1287
|
}
|
|
1232
1288
|
|
|
1289
|
+
public renderSelectedItemDefault(option: T): TemplateResult {
|
|
1290
|
+
const renderFn = this.renderOption || this.renderOptionDefault;
|
|
1291
|
+
return renderFn(option, true);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1233
1294
|
public serializeValue(value: any): string {
|
|
1234
1295
|
// static options just use their value
|
|
1235
1296
|
if (!this.jsonValue && (this.staticOptions.length > 0 || this.tags)) {
|
|
@@ -1241,8 +1302,8 @@ export class Select extends FormElement {
|
|
|
1241
1302
|
|
|
1242
1303
|
public setSelection(value: string): void {
|
|
1243
1304
|
for (const option of this.staticOptions) {
|
|
1244
|
-
if (this.getValue(option
|
|
1245
|
-
if (this.values.length === 0 || this.values[0].value
|
|
1305
|
+
if (this.getValue(option) === value) {
|
|
1306
|
+
if (this.values.length === 0 || this.values[0].value != '' + value) {
|
|
1246
1307
|
this.setValues([option]);
|
|
1247
1308
|
}
|
|
1248
1309
|
return;
|
|
@@ -1258,34 +1319,43 @@ export class Select extends FormElement {
|
|
|
1258
1319
|
this.visibleOptions = [];
|
|
1259
1320
|
this.requestUpdate();
|
|
1260
1321
|
}
|
|
1322
|
+
|
|
1323
|
+
this.fireCustomEvent(CustomEventType.Selection, {
|
|
1324
|
+
selected: null
|
|
1325
|
+
});
|
|
1261
1326
|
}
|
|
1262
1327
|
|
|
1263
1328
|
public setValues(values: any[]) {
|
|
1329
|
+
const oldValues = this.values;
|
|
1264
1330
|
this.values = values;
|
|
1265
|
-
this.requestUpdate('values');
|
|
1331
|
+
this.requestUpdate('values', oldValues);
|
|
1266
1332
|
}
|
|
1267
1333
|
|
|
1268
1334
|
public addValue(value: any) {
|
|
1335
|
+
const oldValues = [...this.values];
|
|
1269
1336
|
this.values.push(value);
|
|
1270
|
-
this.requestUpdate('values');
|
|
1337
|
+
this.requestUpdate('values', oldValues);
|
|
1271
1338
|
}
|
|
1272
1339
|
|
|
1273
1340
|
public removeValue(valueToRemove: any) {
|
|
1341
|
+
const oldValues = [...this.values];
|
|
1274
1342
|
const idx = this.values.indexOf(valueToRemove);
|
|
1275
1343
|
if (idx > -1) {
|
|
1276
1344
|
this.values.splice(idx, 1);
|
|
1277
1345
|
}
|
|
1278
|
-
this.requestUpdate('values');
|
|
1346
|
+
this.requestUpdate('values', oldValues);
|
|
1279
1347
|
}
|
|
1280
1348
|
|
|
1281
1349
|
public popValue() {
|
|
1350
|
+
const oldValues = [...this.values];
|
|
1282
1351
|
this.values.pop();
|
|
1283
|
-
this.requestUpdate('values');
|
|
1352
|
+
this.requestUpdate('values', oldValues);
|
|
1284
1353
|
}
|
|
1285
1354
|
|
|
1286
1355
|
public clear() {
|
|
1356
|
+
const oldValues = this.values;
|
|
1287
1357
|
this.values = [];
|
|
1288
|
-
this.requestUpdate('values');
|
|
1358
|
+
this.requestUpdate('values', oldValues);
|
|
1289
1359
|
}
|
|
1290
1360
|
|
|
1291
1361
|
public render(): TemplateResult {
|
|
@@ -1361,11 +1431,17 @@ export class Select extends FormElement {
|
|
|
1361
1431
|
class="select-container ${classes}"
|
|
1362
1432
|
@click=${this.handleContainerClick}
|
|
1363
1433
|
>
|
|
1364
|
-
|
|
1365
|
-
<div class="left-side">
|
|
1434
|
+
<div class="left-side" >
|
|
1366
1435
|
<slot name="prefix"></slot>
|
|
1367
|
-
<div class="selected">
|
|
1368
|
-
${
|
|
1436
|
+
<div class="selected" >
|
|
1437
|
+
${
|
|
1438
|
+
this.resolving
|
|
1439
|
+
? html`<temba-loading
|
|
1440
|
+
style="margin-left:1em"
|
|
1441
|
+
></temba-loading>`
|
|
1442
|
+
: null
|
|
1443
|
+
}
|
|
1444
|
+
${!this.multi && !this.resolving ? input : null}
|
|
1369
1445
|
${this.values.map(
|
|
1370
1446
|
(selected: any, index: number) => html`
|
|
1371
1447
|
<div
|
|
@@ -1378,12 +1454,6 @@ export class Select extends FormElement {
|
|
|
1378
1454
|
<div
|
|
1379
1455
|
class="remove-item"
|
|
1380
1456
|
style="margin-top:1px"
|
|
1381
|
-
@mousedown=${() => {
|
|
1382
|
-
this.removingSelection = true;
|
|
1383
|
-
}}
|
|
1384
|
-
@mouseup=${() => {
|
|
1385
|
-
this.removingSelection = false;
|
|
1386
|
-
}}
|
|
1387
1457
|
@click=${(evt: MouseEvent) => {
|
|
1388
1458
|
evt.preventDefault();
|
|
1389
1459
|
evt.stopPropagation();
|
|
@@ -1412,7 +1482,7 @@ export class Select extends FormElement {
|
|
|
1412
1482
|
${
|
|
1413
1483
|
!this.tags
|
|
1414
1484
|
? html`<div
|
|
1415
|
-
class="right-side"
|
|
1485
|
+
class="right-side arrow"
|
|
1416
1486
|
style="display:block;margin-right:5px"
|
|
1417
1487
|
@click=${this.handleArrowClick}
|
|
1418
1488
|
>
|
|
@@ -1439,13 +1509,13 @@ export class Select extends FormElement {
|
|
|
1439
1509
|
.cursorIndex=${this.cursorIndex}
|
|
1440
1510
|
.renderOptionDetail=${this.renderOptionDetail}
|
|
1441
1511
|
.renderOptionName=${this.renderOptionName}
|
|
1442
|
-
.renderOption=${this.renderOption}
|
|
1512
|
+
.renderOption=${this.renderOption || this.renderOptionDefault}
|
|
1443
1513
|
.anchorTo=${this.anchorElement}
|
|
1444
1514
|
.options=${this.visibleOptions}
|
|
1445
1515
|
.spaceSelect=${this.spaceSelect}
|
|
1446
1516
|
.nameKey=${this.nameKey}
|
|
1447
1517
|
.getName=${this.getNameInternal}
|
|
1448
|
-
static-width=${this.optionWidth}
|
|
1518
|
+
?static-width=${this.optionWidth}
|
|
1449
1519
|
?anchor-right=${this.anchorRight}
|
|
1450
1520
|
?visible=${this.visibleOptions.length > 0}
|
|
1451
1521
|
></temba-options>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { css, CSSResultArray, html, TemplateResult } from 'lit';
|
|
2
|
+
import { Select, SelectOption } from './Select';
|
|
3
|
+
import { property } from 'lit/decorators.js';
|
|
4
|
+
import { getFullName } from '../user/TembaUser';
|
|
5
|
+
|
|
6
|
+
export interface UserOption extends SelectOption {
|
|
7
|
+
email: string;
|
|
8
|
+
name: string;
|
|
9
|
+
avatar: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class UserSelect extends Select<UserOption> {
|
|
13
|
+
static get styles(): CSSResultArray {
|
|
14
|
+
return [
|
|
15
|
+
super.styles,
|
|
16
|
+
css`
|
|
17
|
+
:host {
|
|
18
|
+
width: 150px;
|
|
19
|
+
display: block;
|
|
20
|
+
}
|
|
21
|
+
`
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@property({ type: String })
|
|
26
|
+
endpoint = '/api/v2/users.json';
|
|
27
|
+
|
|
28
|
+
@property({ type: String })
|
|
29
|
+
nameKey = 'name';
|
|
30
|
+
|
|
31
|
+
@property({ type: String })
|
|
32
|
+
valueKey = 'email';
|
|
33
|
+
|
|
34
|
+
@property({ type: String })
|
|
35
|
+
placeholder: string = 'Select a user';
|
|
36
|
+
|
|
37
|
+
@property({ type: Boolean })
|
|
38
|
+
sorted: boolean = true;
|
|
39
|
+
|
|
40
|
+
@property({ type: Object })
|
|
41
|
+
user: UserOption;
|
|
42
|
+
|
|
43
|
+
constructor() {
|
|
44
|
+
super();
|
|
45
|
+
this.shouldExclude = (option: UserOption) => {
|
|
46
|
+
const selected = this.values[0];
|
|
47
|
+
return option.email === selected?.email;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public prepareOptionsDefault(options: UserOption[]): UserOption[] {
|
|
52
|
+
options.forEach((option) => {
|
|
53
|
+
option.name = getFullName(option);
|
|
54
|
+
});
|
|
55
|
+
return options;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public renderOptionDefault(option: UserOption): TemplateResult {
|
|
59
|
+
if (!option) {
|
|
60
|
+
return html``;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return html`<temba-user
|
|
64
|
+
email=${option.email}
|
|
65
|
+
name=${option.name}
|
|
66
|
+
avatar=${option.avatar}
|
|
67
|
+
scale="0.8"
|
|
68
|
+
showname
|
|
69
|
+
></temba-user>`;
|
|
70
|
+
}
|
|
71
|
+
}
|