@nyaruka/temba-components 0.114.1 → 0.117.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/temba-components.js +329 -311
  3. package/dist/temba-components.js.map +1 -1
  4. package/out-tsc/src/button/Button.js +5 -0
  5. package/out-tsc/src/button/Button.js.map +1 -1
  6. package/out-tsc/src/chat/Chat.js +5 -1
  7. package/out-tsc/src/chat/Chat.js.map +1 -1
  8. package/out-tsc/src/contacts/ContactChat.js +27 -16
  9. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  10. package/out-tsc/src/dropdown/Dropdown.js +1 -1
  11. package/out-tsc/src/dropdown/Dropdown.js.map +1 -1
  12. package/out-tsc/src/list/TembaList.js +3 -2
  13. package/out-tsc/src/list/TembaList.js.map +1 -1
  14. package/out-tsc/src/list/TembaMenu.js +17 -5
  15. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  16. package/out-tsc/src/options/Options.js +17 -17
  17. package/out-tsc/src/options/Options.js.map +1 -1
  18. package/out-tsc/src/select/Select.js +6 -2
  19. package/out-tsc/src/select/Select.js.map +1 -1
  20. package/out-tsc/src/select/UserSelect.js +0 -1
  21. package/out-tsc/src/select/UserSelect.js.map +1 -1
  22. package/out-tsc/src/select/WorkspaceSelect.js +65 -0
  23. package/out-tsc/src/select/WorkspaceSelect.js.map +1 -0
  24. package/out-tsc/src/store/Store.js +3 -1
  25. package/out-tsc/src/store/Store.js.map +1 -1
  26. package/out-tsc/src/user/TembaUser.js +27 -11
  27. package/out-tsc/src/user/TembaUser.js.map +1 -1
  28. package/out-tsc/temba-modules.js +2 -0
  29. package/out-tsc/temba-modules.js.map +1 -1
  30. package/out-tsc/test/temba-contact-chat.test.js +1 -0
  31. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  32. package/package.json +1 -1
  33. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  34. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  35. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  36. package/screenshots/truth/select/expressions.png +0 -0
  37. package/screenshots/truth/select/functions.png +0 -0
  38. package/src/button/Button.ts +5 -0
  39. package/src/chat/Chat.ts +5 -1
  40. package/src/contacts/ContactChat.ts +30 -20
  41. package/src/dropdown/Dropdown.ts +1 -1
  42. package/src/list/TembaList.ts +3 -6
  43. package/src/list/TembaMenu.ts +20 -5
  44. package/src/options/Options.ts +18 -18
  45. package/src/select/Select.ts +5 -2
  46. package/src/select/UserSelect.ts +0 -1
  47. package/src/select/WorkspaceSelect.ts +71 -0
  48. package/src/store/Store.ts +3 -1
  49. package/src/user/TembaUser.ts +23 -10
  50. package/temba-modules.ts +2 -0
  51. package/test/temba-contact-chat.test.ts +5 -0
@@ -8,6 +8,7 @@ import { Dropdown } from '../dropdown/Dropdown';
8
8
  import { NotificationList } from './NotificationList';
9
9
  import { ResizeElement } from '../ResizeElement';
10
10
  import { Store } from '../store/Store';
11
+
11
12
  export interface MenuItem {
12
13
  id?: string;
13
14
  vanity_id?: string;
@@ -32,6 +33,7 @@ export interface MenuItem {
32
33
  trigger?: boolean;
33
34
  event?: string;
34
35
  mobile?: boolean;
36
+ initial?: string;
35
37
  }
36
38
 
37
39
  interface MenuItemState {
@@ -587,11 +589,6 @@ export class TembaMenu extends ResizeElement {
587
589
  margin-right: 0.75em;
588
590
  }
589
591
 
590
- temba-button[lined] {
591
- margin: 0.2em 0;
592
- display: block;
593
- }
594
-
595
592
  .expand-icon {
596
593
  transform: rotate(180deg);
597
594
  --icon-color: rgba(255, 255, 255, 0.5);
@@ -653,6 +650,14 @@ export class TembaMenu extends ResizeElement {
653
650
  .level-0 .icon-wrapper {
654
651
  padding: 0.4em 0.9em;
655
652
  }
653
+
654
+ temba-workspace-select {
655
+ margin: 0.2em;
656
+ display: block;
657
+ --options-shadow: none;
658
+ --color-widget-border: transparent;
659
+ --widget-box-shadow: none;
660
+ }
656
661
  `;
657
662
  }
658
663
 
@@ -1062,6 +1067,16 @@ export class TembaMenu extends ResizeElement {
1062
1067
  return html`<div class="divider"></div>`;
1063
1068
  }
1064
1069
 
1070
+ if (menuItem.type === 'temba-workspace-select') {
1071
+ return html`<temba-workspace-select
1072
+ @change=${(event) => {
1073
+ event.stopPropagation();
1074
+ event.preventDefault();
1075
+ }}
1076
+ .values=${[JSON.parse(menuItem.initial)]}
1077
+ ></temba-workspace-select>`;
1078
+ }
1079
+
1065
1080
  if (menuItem.type === 'temba-notification-list') {
1066
1081
  return html`<temba-notification-list
1067
1082
  endpoint=${menuItem.href}
@@ -17,16 +17,14 @@ export class Options extends RapidElement {
17
17
  user-select: none;
18
18
  border-radius: var(--curvature-widget);
19
19
  overflow: hidden;
20
- margin-top: var(--options-margin-top);
21
20
  display: flex;
22
21
  flex-direction: column;
23
- // transform: scaleY(0.5) translateY(-5em);
24
22
  transition: transform var(--transition-speed)
25
23
  cubic-bezier(0.71, 0.18, 0.61, 1.33),
26
24
  opacity var(--transition-speed) cubic-bezier(0.71, 0.18, 0.61, 1.33);
27
25
  opacity: 0;
28
- border: 1px transparent;
29
26
  z-index: 1000;
27
+ pointer-events: none;
30
28
  }
31
29
 
32
30
  .shadow {
@@ -97,10 +95,11 @@ export class Options extends RapidElement {
97
95
  }
98
96
 
99
97
  .show {
100
- // transform: scaleY(1) translateY(0);
101
98
  border: 1px solid var(--color-widget-border);
102
99
  opacity: 1;
103
100
  z-index: 1;
101
+ pointer-events: auto;
102
+ margin-top: var(--options-margin-top);
104
103
  }
105
104
 
106
105
  .option {
@@ -351,7 +350,7 @@ export class Options extends RapidElement {
351
350
  this.tempOptions = changed.get('options');
352
351
  window.setTimeout(() => {
353
352
  this.tempOptions = [];
354
- }, 0);
353
+ }, 200);
355
354
  }
356
355
  }
357
356
 
@@ -372,7 +371,7 @@ export class Options extends RapidElement {
372
371
  (previousCount === 0 && newCount > 0 && !changed.has('cursorIndex'))
373
372
  ) {
374
373
  if (!this.internalFocusDisabled) {
375
- if (!this.block || this.cursorIndex === -1) {
374
+ if (!this.block) {
376
375
  this.cursorIndex = 0;
377
376
  } else {
378
377
  if (this.cursorIndex >= newCount) {
@@ -444,6 +443,10 @@ export class Options extends RapidElement {
444
443
  }
445
444
  }
446
445
 
446
+ if (index === -1) {
447
+ return;
448
+ }
449
+
447
450
  const selected = this.options[index];
448
451
  this.fireCustomEvent(CustomEventType.Selection, {
449
452
  selected,
@@ -564,17 +567,12 @@ export class Options extends RapidElement {
564
567
  .getBoundingClientRect();
565
568
 
566
569
  if (this.anchorTo) {
567
- this.top = 0;
568
570
  const anchorBounds = this.anchorTo.getBoundingClientRect();
571
+ this.top = anchorBounds.bottom;
569
572
  if (anchorBounds.bottom + optionsBounds.height > window.innerHeight) {
570
573
  this.top = -(optionsBounds.height + anchorBounds.height + 20);
571
574
  }
572
575
  this.left = anchorBounds.left;
573
-
574
- // adjust for parent scrolling
575
- if (this.scrollParent) {
576
- this.top += -this.scrollParent.scrollTop;
577
- }
578
576
  }
579
577
  }
580
578
  }
@@ -627,13 +625,15 @@ export class Options extends RapidElement {
627
625
  vertical *= -1;
628
626
  }
629
627
 
630
- const containerStyle = {
631
- 'margin-left': `${this.marginHorizontal}px`,
632
- 'margin-top': `${vertical}px`
633
- };
628
+ const containerStyle = this.visible
629
+ ? {
630
+ 'margin-left': `${this.marginHorizontal}px`,
631
+ 'margin-top': `${vertical}px`
632
+ }
633
+ : {};
634
634
 
635
635
  if (this.top) {
636
- containerStyle[`transform`] = `translateY(${this.top}px)`;
636
+ containerStyle['top'] = `${this.top}px`;
637
637
  }
638
638
 
639
639
  if (this.left) {
@@ -649,7 +649,7 @@ export class Options extends RapidElement {
649
649
  'options-container': true,
650
650
  show: this.visible,
651
651
  top: this.poppedTop,
652
- anchored: !this.block,
652
+ anchored: !this.block && !!this.anchorTo,
653
653
  loading: this.loading,
654
654
  shadow: !this.hideShadow,
655
655
  bordered: this.hideShadow
@@ -499,6 +499,9 @@ export class Select<T extends SelectOption> extends FormElement {
499
499
  @property({ type: Array, attribute: 'options' })
500
500
  private staticOptions: any[] = [];
501
501
 
502
+ @property({ type: Boolean })
503
+ allowAnchor: boolean = true;
504
+
502
505
  private alphaSort = (a: any, b: any) => {
503
506
  // by default, all endpoint values are sorted by name
504
507
  if (this.endpoint) {
@@ -1510,7 +1513,7 @@ export class Select<T extends SelectOption> extends FormElement {
1510
1513
  .renderOptionDetail=${this.renderOptionDetail}
1511
1514
  .renderOptionName=${this.renderOptionName}
1512
1515
  .renderOption=${this.renderOption || this.renderOptionDefault}
1513
- .anchorTo=${this.anchorElement}
1516
+ .anchorTo=${this.allowAnchor ? this.anchorElement : null}
1514
1517
  .options=${this.visibleOptions}
1515
1518
  .spaceSelect=${this.spaceSelect}
1516
1519
  .nameKey=${this.nameKey}
@@ -1523,7 +1526,7 @@ export class Select<T extends SelectOption> extends FormElement {
1523
1526
  <temba-options
1524
1527
  @temba-selection=${this.handleExpressionSelection}
1525
1528
  @temba-canceled=${() => {}}
1526
- .anchorTo=${this.anchorExpressions}
1529
+ .anchorTo=${this.allowAnchor ? this.anchorExpressions : null}
1527
1530
  .options=${this.completionOptions}
1528
1531
  .renderOption=${renderCompletionOption}
1529
1532
  ?visible=${this.completionOptions.length > 0}
@@ -64,7 +64,6 @@ export class UserSelect extends Select<UserOption> {
64
64
  email=${option.email}
65
65
  name=${option.name}
66
66
  avatar=${option.avatar}
67
- scale="0.8"
68
67
  showname
69
68
  ></temba-user>`;
70
69
  }
@@ -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 { getScrollParent } from '../utils';
5
+
6
+ export interface WorkspaceOption extends SelectOption {
7
+ name: string;
8
+ id: string;
9
+ type: string;
10
+ }
11
+
12
+ export class WorkspaceSelect extends Select<WorkspaceOption> {
13
+ static get styles(): CSSResultArray {
14
+ return [
15
+ super.styles,
16
+ css`
17
+ :host {
18
+ border: 0px solid blue;
19
+ }
20
+ `
21
+ ];
22
+ }
23
+
24
+ @property({ type: String })
25
+ endpoint = '/api/internal/orgs.json';
26
+
27
+ @property({ type: String })
28
+ nameKey = 'name';
29
+
30
+ @property({ type: String })
31
+ valueKey = 'id';
32
+
33
+ @property({ type: String })
34
+ placeholder: string = 'Choose Workspace';
35
+
36
+ @property({ type: Boolean })
37
+ sorted: boolean = true;
38
+
39
+ @property({ type: Object })
40
+ workspace: WorkspaceOption;
41
+
42
+ constructor() {
43
+ super();
44
+ this.shouldExclude = (option: WorkspaceOption) => {
45
+ const selected = this.values[0];
46
+ return option.id === selected?.id;
47
+ };
48
+
49
+ this.searchable = true;
50
+ }
51
+
52
+ public firstUpdated(changed: Map<string, any>) {
53
+ super.firstUpdated(changed);
54
+ this.allowAnchor = !!getScrollParent(this);
55
+ }
56
+
57
+ public prepareOptionsDefault(options: WorkspaceOption[]): WorkspaceOption[] {
58
+ options.forEach((option) => {
59
+ option.type = 'workspace';
60
+ });
61
+ return options;
62
+ }
63
+
64
+ public renderOptionDefault(option: WorkspaceOption): TemplateResult {
65
+ if (!option) {
66
+ return html``;
67
+ }
68
+
69
+ return html`<temba-user name=${option.name} showname></temba-user>`;
70
+ }
71
+ }
@@ -519,7 +519,9 @@ export class Store extends RapidElement {
519
519
  // we don't want to fetch all users at once so we can benefit from caching
520
520
  emails.forEach((email) => {
521
521
  promises.push(
522
- this.getUrl(`/api/v2/users.json?email=${encodeURIComponent(email)}`)
522
+ this.getUrl(`/api/v2/users.json?email=${encodeURIComponent(email)}`, {
523
+ force: true
524
+ })
523
525
  );
524
526
  });
525
527
 
@@ -48,7 +48,10 @@ export class TembaUser extends RapidElement {
48
48
  system: boolean;
49
49
 
50
50
  @property({ type: String, attribute: false })
51
- background: string = '#e6e6e6';
51
+ bgimage: string = null;
52
+
53
+ @property({ type: String, attribute: false })
54
+ bgcolor: string = '#e6e6e6';
52
55
 
53
56
  @property({ type: String, attribute: false })
54
57
  initials: string = '';
@@ -68,16 +71,25 @@ export class TembaUser extends RapidElement {
68
71
  super.updated(changed);
69
72
 
70
73
  if (changed.has('system') && this.system) {
71
- this.background = `url('${DEFAULT_AVATAR}') center / contain no-repeat`;
74
+ this.bgimage = `url('${DEFAULT_AVATAR}') center / contain no-repeat`;
72
75
  }
73
76
 
74
- if (changed.has('name') && this.name) {
75
- this.background = colorHash.hex(this.name);
76
- this.initials = extractInitials(this.name);
77
+ if (changed.has('name')) {
78
+ if (this.name) {
79
+ this.bgcolor = colorHash.hex(this.name);
80
+ this.initials = extractInitials(this.name);
81
+ } else {
82
+ this.bgcolor = '#e6e6e6';
83
+ this.initials = '';
84
+ }
77
85
  }
78
86
 
79
- if (changed.has('avatar') && this.avatar) {
80
- this.background = `url('${this.avatar}') center / contain no-repeat`;
87
+ if (changed.has('avatar')) {
88
+ if (this.avatar) {
89
+ this.bgimage = `url('${this.avatar}') center / contain no-repeat`;
90
+ } else if (!this.system) {
91
+ this.bgimage = null;
92
+ }
81
93
  }
82
94
  }
83
95
 
@@ -96,11 +108,12 @@ export class TembaUser extends RapidElement {
96
108
  border-radius: 100%;
97
109
  font-weight: 400;
98
110
  overflow: hidden;
99
- font-size: 12px;
111
+ font-size: 0.8em;
112
+ margin-right: 0.75em;
100
113
  box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.1);
101
- background:${this.background}"
114
+ background:${this.bgimage || this.bgcolor};"
102
115
  >
103
- ${this.initials && !this.avatar
116
+ ${this.initials && !this.bgimage
104
117
  ? html` <div
105
118
  style="border: 0px solid red; display:flex; flex-direction: column; align-items:center;flex-grow:1"
106
119
  >
package/temba-modules.ts CHANGED
@@ -63,6 +63,7 @@ import { StartProgress } from './src/progress/StartProgress';
63
63
  import { ShortcutList } from './src/list/ShortcutList';
64
64
  import { PopupSelect } from './src/select/PopupSelect';
65
65
  import { UserSelect } from './src/select/UserSelect';
66
+ import { WorkspaceSelect } from './src/select/WorkspaceSelect';
66
67
 
67
68
  export function addCustomElement(name: string, comp: any) {
68
69
  if (!window.customElements.get(name)) {
@@ -136,3 +137,4 @@ addCustomElement('temba-start-progress', StartProgress);
136
137
  addCustomElement('temba-shortcuts', ShortcutList);
137
138
  addCustomElement('temba-popup-select', PopupSelect);
138
139
  addCustomElement('temba-user-select', UserSelect);
140
+ addCustomElement('temba-workspace-select', WorkspaceSelect);
@@ -60,6 +60,11 @@ describe('temba-contact-chat', () => {
60
60
  '/test-assets/contacts/history.json'
61
61
  );
62
62
 
63
+ mockGET(
64
+ /\/api\/v2\/users\.json\?email=admin1%40nyaruka\.com/,
65
+ '/test-assets/api/users/admin1.json'
66
+ );
67
+
63
68
  mockAPI();
64
69
  clock = useFakeTimers();
65
70
  });