@nyaruka/temba-components 0.39.0 → 0.40.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 (44) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/{9ac0723e.js → b885f7d6.js} +7 -34
  3. package/dist/index.js +7 -34
  4. package/dist/sw.js +1 -1
  5. package/dist/sw.js.map +1 -1
  6. package/dist/templates/components-body.html +1 -1
  7. package/dist/templates/components-head.html +1 -1
  8. package/out-tsc/src/contacts/ContactBadges.js +5 -5
  9. package/out-tsc/src/contacts/ContactBadges.js.map +1 -1
  10. package/out-tsc/src/list/TembaMenu.js +77 -168
  11. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  12. package/out-tsc/src/utils/index.js +2 -0
  13. package/out-tsc/src/utils/index.js.map +1 -1
  14. package/out-tsc/src/vectoricon/index.js +2 -0
  15. package/out-tsc/src/vectoricon/index.js.map +1 -1
  16. package/out-tsc/test/temba-label.test.js +4 -4
  17. package/out-tsc/test/temba-label.test.js.map +1 -1
  18. package/out-tsc/test/temba-menu.test.js +58 -7
  19. package/out-tsc/test/temba-menu.test.js.map +1 -1
  20. package/package.json +1 -1
  21. package/screenshots/truth/label/custom.png +0 -0
  22. package/screenshots/truth/label/danger.png +0 -0
  23. package/screenshots/truth/label/default-icon.png +0 -0
  24. package/screenshots/truth/label/shadow.png +0 -0
  25. package/screenshots/truth/menu/menu-focus.png +0 -0
  26. package/screenshots/truth/menu/menu-focused-with items.png +0 -0
  27. package/screenshots/truth/menu/menu-refresh-1.png +0 -0
  28. package/screenshots/truth/menu/menu-refresh-2.png +0 -0
  29. package/screenshots/truth/menu/menu-root.png +0 -0
  30. package/screenshots/truth/menu/menu-submenu.png +0 -0
  31. package/screenshots/truth/menu/menu-tasks-nextup.png +0 -0
  32. package/screenshots/truth/menu/menu-tasks.png +0 -0
  33. package/src/contacts/ContactBadges.ts +5 -5
  34. package/src/list/TembaMenu.ts +89 -181
  35. package/src/utils/index.ts +3 -0
  36. package/src/vectoricon/index.ts +2 -0
  37. package/test/temba-label.test.ts +4 -4
  38. package/test/temba-menu.test.ts +73 -7
  39. package/test-assets/menu/menu-root.json +33 -0
  40. package/test-assets/menu/menu-schedule.json +21 -0
  41. package/test-assets/{list → menu}/menu-tasks.json +0 -0
  42. package/screenshots/truth/list/menu-root.png +0 -0
  43. package/screenshots/truth/list/menu-submenu.png +0 -0
  44. package/test-assets/list/menu-root.json +0 -17
@@ -1 +1 @@
1
- {"version":3,"file":"temba-menu.test.js","sourceRoot":"","sources":["../../test/temba-menu.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvE,MAAM,GAAG,GAAG,YAAY,CAAC;AACzB,MAAM,OAAO,GAAG,KAAK,EAAE,QAAa,EAAE,EAAE,KAAK,GAAG,CAAC,EAAE,EAAE;IACnD,MAAM,IAAI,GAAG,CAAC,MAAM,YAAY,CAC9B,GAAG,EACH,KAAK,EACL,EAAE,EACF,KAAK,EACL,CAAC,EACD,sBAAsB,CACvB,CAAc,CAAC;IAEhB,qBAAqB;IACrB,MAAM,IAAI,CAAC,YAAY,CAAC;IAExB,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,gBAAgB,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,IAAI,GAAc,MAAM,OAAO,EAAE,CAAC;QACxC,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;QACrC,MAAM,IAAI,GAAc,MAAM,OAAO,CAAC;YACpC,QAAQ,EAAE,kCAAkC;SAC7C,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3C,MAAM,gBAAgB,CAAC,gBAAgB,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kBAAkB,EAAE,KAAK,IAAI,EAAE;QAChC,MAAM,IAAI,GAAc,MAAM,OAAO,CAAC;YACpC,QAAQ,EAAE,kCAAkC;SAC7C,CAAC,CAAC;QAEH,uBAAuB;QACvB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,KAAK,EAAE,CAAC;QACnC,MAAM,IAAI,CAAC,YAAY,CAAC;QAExB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAEpD,MAAM,gBAAgB,CAAC,mBAAmB,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { assert, expect } from '@open-wc/testing';\n\nimport { TembaMenu } from '../src/list/TembaMenu';\nimport { assertScreenshot, getClip, getComponent } from './utils.test';\n\nconst TAG = 'temba-menu';\nconst getMenu = async (attrs: any = {}, width = 0) => {\n const menu = (await getComponent(\n TAG,\n attrs,\n '',\n width,\n 0,\n 'display:inline-block'\n )) as TembaMenu;\n\n // wait for the fetch\n await menu.httpComplete;\n\n return menu;\n};\n\ndescribe('temba-menu', () => {\n it('can be created', async () => {\n const list: TembaMenu = await getMenu();\n assert.instanceOf(list, TembaMenu);\n expect(list.root).is.undefined;\n });\n\n it('renders with endpoint', async () => {\n const menu: TembaMenu = await getMenu({\n endpoint: '/test-assets/list/menu-root.json',\n });\n\n expect(menu.root.items.length).to.equal(2);\n await assertScreenshot('list/menu-root', getClip(menu));\n });\n\n it('supports submenu', async () => {\n const menu: TembaMenu = await getMenu({\n endpoint: '/test-assets/list/menu-root.json',\n });\n\n // click our first item\n menu.getDiv('#menu-tasks').click();\n await menu.httpComplete;\n\n expect(menu.root.items[0].items.length).to.equal(3);\n\n await assertScreenshot('list/menu-submenu', getClip(menu));\n });\n});\n"]}
1
+ {"version":3,"file":"temba-menu.test.js","sourceRoot":"","sources":["../../test/temba-menu.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { assert, expect } from '@open-wc/testing';\n\nimport { TembaMenu } from '../src/list/TembaMenu';\nimport { assertScreenshot, getClip, getComponent } from './utils.test';\n\nconst TAG = 'temba-menu';\nconst getMenu = async (attrs: any = {}, width = 0) => {\n const menu = (await getComponent(\n TAG,\n attrs,\n '',\n width,\n 0,\n 'display:inline-block'\n )) as TembaMenu;\n\n // wait for the fetch\n await menu.httpComplete;\n\n return menu;\n};\n\nconst IDX_CHOOSER = 0;\nconst IDX_TASKS = 1;\nconst IDX_SCHEDULE = 2;\n\ndescribe('temba-menu', () => {\n it('can be created', async () => {\n const list: TembaMenu = await getMenu();\n assert.instanceOf(list, TembaMenu);\n expect(list.root).is.undefined;\n });\n\n it('renders with endpoint', async () => {\n const menu: TembaMenu = await getMenu({\n endpoint: '/test-assets/menu/menu-root.json',\n });\n\n expect(menu.root.items.length).to.equal(3);\n await assertScreenshot('menu/menu-root', getClip(menu));\n });\n\n it('supports submenu', async () => {\n const menu: TembaMenu = await getMenu({\n endpoint: '/test-assets/menu/menu-root.json',\n });\n\n // click our tasks\n menu.getDiv('#menu-tasks').click();\n await menu.httpComplete;\n menu.requestUpdate();\n // await menu.updateComplete;\n\n expect(menu.root.items[IDX_TASKS].items.length).to.equal(3);\n await assertScreenshot('menu/menu-submenu', getClip(menu));\n });\n\n it('sets focus', async () => {\n // setting focus just shows the selection, it does\n // not trigger events such as loading or dispatching\n\n const menu: TembaMenu = await getMenu({\n endpoint: '/test-assets/menu/menu-root.json',\n });\n\n // click our tasks\n menu.getDiv('#menu-tasks').click();\n await menu.httpComplete;\n\n // now set the focus manually\n menu.setFocusedItem('schedule');\n\n // setting focus does NOT fetch items\n expect(menu.root.items[IDX_SCHEDULE].items).to.equal(undefined);\n\n // now load the items explicitly\n menu.getDiv('#menu-schedule').click();\n await menu.httpComplete;\n expect(menu.root.items[IDX_SCHEDULE].items.length).to.equal(3);\n\n await assertScreenshot('menu/menu-focused-with items', getClip(menu));\n\n menu.setFocusedItem('tasks');\n await assertScreenshot('menu/menu-tasks', getClip(menu));\n\n menu.setFocusedItem('tasks/todo');\n await assertScreenshot('menu/menu-tasks-nextup', getClip(menu));\n });\n\n it('refreshes', async () => {\n // the menu should refresh along the selection path without destroying state\n const menu: TembaMenu = await getMenu({\n endpoint: '/test-assets/menu/menu-root.json',\n });\n\n // click our tasks\n menu.getDiv('#menu-tasks').click();\n menu.requestUpdate();\n await menu.httpComplete;\n // await menu.updateComplete;\n\n // now click on the todo\n menu.getDiv('#menu-todo').click();\n menu.requestUpdate();\n // await menu.updateComplete;\n\n expect(menu.root.items[IDX_TASKS].items.length).to.equal(3);\n await assertScreenshot('menu/menu-refresh-1', getClip(menu));\n\n // now refresh!\n menu.refresh();\n await menu.httpComplete;\n\n // we should still have our task items\n expect(menu.root.items[IDX_TASKS].items.length).to.equal(3);\n await assertScreenshot('menu/menu-refresh-2', getClip(menu));\n });\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyaruka/temba-components",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "Web components to support rapidpro and related projects",
5
5
  "author": "Nyaruka <code@nyaruka.coim>",
6
6
  "main": "dist/index.js",
Binary file
Binary file
Binary file
@@ -4,9 +4,9 @@ import { Icon } from '../vectoricon';
4
4
  import { ContactStoreElement } from './ContactStoreElement';
5
5
 
6
6
  const STATUS = {
7
- stopped: { name: 'Stopped', icon: 'x-octagon' },
8
- blocked: { name: 'Blocked', icon: 'slash' },
9
- archived: { name: 'Archived', icon: 'archive' },
7
+ stopped: { name: 'Stopped' },
8
+ blocked: { name: 'Blocked' },
9
+ archived: { name: 'Archived' },
10
10
  };
11
11
 
12
12
  export class ContactBadges extends ContactStoreElement {
@@ -32,9 +32,9 @@ export class ContactBadges extends ContactStoreElement {
32
32
  ${status && this.data.status !== 'active'
33
33
  ? html`
34
34
  <temba-label
35
- icon="${status.icon}"
35
+ icon="icon.contact_${this.data.status}"
36
36
  onclick="goto(event)"
37
- href="/contact/${status.name.toLowerCase()}"
37
+ href="/contact/${status.name.toLowerCase()}/"
38
38
  secondary
39
39
  clickable
40
40
  shadow
@@ -18,7 +18,6 @@ export interface MenuItem {
18
18
  loading?: boolean;
19
19
  bottom?: boolean;
20
20
  level?: number;
21
- trigger?: string;
22
21
  href?: string;
23
22
  show_header?: boolean;
24
23
  items?: MenuItem[];
@@ -122,9 +121,6 @@ export class TembaMenu extends RapidElement {
122
121
  display: none;
123
122
  }
124
123
 
125
- .submenu {
126
- }
127
-
128
124
  .level-0 > .item,
129
125
  .level-0 > temba-dropdown > div[slot='toggle'] > .avatar {
130
126
  background: var(--color-primary-dark);
@@ -185,9 +181,6 @@ export class TembaMenu extends RapidElement {
185
181
  font-size: 0.7em;
186
182
  }
187
183
 
188
- .level-0.expanding {
189
- }
190
-
191
184
  .level-0.expanded {
192
185
  background: inherit;
193
186
  }
@@ -218,9 +211,6 @@ export class TembaMenu extends RapidElement {
218
211
  margin-right: 0.5em;
219
212
  }
220
213
 
221
- .item.inline > temba-icon {
222
- }
223
-
224
214
  .item > .details > .name {
225
215
  flex-grow: 1;
226
216
  white-space: nowrap;
@@ -270,9 +260,6 @@ export class TembaMenu extends RapidElement {
270
260
  border-bottom-right-radius: var(--curvature);
271
261
  }
272
262
 
273
- .level-0 > .selected-top {
274
- }
275
-
276
263
  .level-0 > .item:hover {
277
264
  background: rgba(var(--primary-rgb), 0.85);
278
265
  --icon-color: #fff;
@@ -284,12 +271,6 @@ export class TembaMenu extends RapidElement {
284
271
  cursor: default;
285
272
  }
286
273
 
287
- .inline-children {
288
- }
289
-
290
- .inline-children .item {
291
- }
292
-
293
274
  .item.inline {
294
275
  border: 0px solid transparent;
295
276
  }
@@ -330,9 +311,6 @@ export class TembaMenu extends RapidElement {
330
311
  transition: min-width var(--transition-speed) !important;
331
312
  }
332
313
 
333
- .level-1 .item .details {
334
- }
335
-
336
314
  .collapsed .item {
337
315
  overflow: hidden;
338
316
  min-width: 0;
@@ -346,12 +324,6 @@ export class TembaMenu extends RapidElement {
346
324
  align-items: center;
347
325
  }
348
326
 
349
- .item .details .name {
350
- }
351
-
352
- .item temba-icon {
353
- }
354
-
355
327
  .collapsed .item {
356
328
  margin-bottom: 0.5em;
357
329
  }
@@ -536,7 +508,6 @@ export class TembaMenu extends RapidElement {
536
508
 
537
509
  root: MenuItem;
538
510
  selection: string[] = [];
539
- pending: string[] = [];
540
511
  state: { [id: string]: MenuItemState } = {};
541
512
 
542
513
  constructor() {
@@ -566,14 +537,6 @@ export class TembaMenu extends RapidElement {
566
537
  }
567
538
 
568
539
  public updated(changes: Map<string, any>) {
569
- if (changes.has('value')) {
570
- this.setSelection((this.value || '').split('/'));
571
- }
572
-
573
- if (changes.has('submenu') && !changes.has('value')) {
574
- this.setSelection([this.submenu]);
575
- }
576
-
577
540
  if (changes.has('endpoint')) {
578
541
  this.root = {
579
542
  level: -1,
@@ -582,9 +545,16 @@ export class TembaMenu extends RapidElement {
582
545
 
583
546
  if (!this.wait) {
584
547
  this.loadItems(this.root);
548
+ } else {
549
+ this.fireCustomEvent(CustomEventType.Ready);
585
550
  }
551
+ }
586
552
 
587
- this.fireCustomEvent(CustomEventType.Ready);
553
+ if (changes.has('root')) {
554
+ if (this.value) {
555
+ this.setFocusedItem(this.value);
556
+ this.value = null;
557
+ }
588
558
  }
589
559
  }
590
560
 
@@ -592,53 +562,36 @@ export class TembaMenu extends RapidElement {
592
562
  this.loadItems(this.root);
593
563
  }
594
564
 
595
- public refresh(path: string[] = null) {
596
- if (!path) {
597
- path = [...this.selection];
598
- }
599
-
600
- // go up the tree until we find an endpoint
601
- const item = this.getMenuItemForSelection(path);
565
+ public refresh() {
566
+ const path = [...this.selection];
567
+ let item = this.root;
602
568
 
603
- if (item) {
604
- if (item.endpoint) {
605
- this.loadItems(item, false);
606
- } else {
607
- path.pop();
608
- this.refresh(path);
609
- }
569
+ while (path.length > 0) {
570
+ this.loadItems(item);
571
+ const id = path.shift();
572
+ item = item.items.find(_item => _item.id == id);
610
573
  }
611
- }
612
-
613
- private fireNoPath(missingId: string) {
614
- const item = this.getMenuItem();
615
-
616
- if (item) {
617
- const details = {
618
- item: item.id,
619
- selection: '/' + this.selection.join('/'),
620
- endpoint: item.endpoint,
621
- path:
622
- missingId + '/' + this.pending.join('/') + document.location.search,
623
- };
624
-
625
- // remove any excess from our selection
626
- const selection = this.selection.join('/');
627
- selection.replace(details.path, '');
628
- this.selection = selection.split('/');
629
574
 
630
- this.fireCustomEvent(CustomEventType.NoPath, details);
631
- this.pending = [];
632
- this.requestUpdate('root');
633
- }
575
+ this.loadItems(item);
634
576
  }
635
577
 
636
578
  // eslint-disable-next-line @typescript-eslint/no-empty-function
637
- private loadItems(item: MenuItem, selectFirst = true) {
579
+ private loadItems(item: MenuItem, selectFirst = false) {
638
580
  if (item && item.endpoint) {
639
581
  item.loading = true;
640
582
  this.httpComplete = fetchResults(item.endpoint).then(
641
583
  (items: MenuItem[]) => {
584
+ items.forEach(newItem => {
585
+ if (!newItem.items) {
586
+ const prevItem = (item.items || []).find(
587
+ prev => prev.id == newItem.id
588
+ );
589
+ if (prevItem && prevItem.items) {
590
+ newItem.items = prevItem.items;
591
+ }
592
+ }
593
+ });
594
+
642
595
  // update our item level
643
596
  items.forEach(subItem => {
644
597
  subItem.level = item.level + 1;
@@ -646,42 +599,30 @@ export class TembaMenu extends RapidElement {
646
599
  if (subItem.items) {
647
600
  subItem.items.forEach(inlineItem => {
648
601
  inlineItem.level = item.level + 2;
649
- // inlineItem.parent = subItem;
650
602
  });
651
603
  }
652
604
  });
653
605
 
654
606
  item.items = items;
655
607
  item.loading = false;
608
+
609
+ if (this.submenu && this.selection.length == 0) {
610
+ const sub = this.getMenuItemForSelection([this.submenu]);
611
+ this.handleItemClicked(null, sub);
612
+ }
613
+
614
+ if (!this.wait) {
615
+ this.fireCustomEvent(CustomEventType.Ready);
616
+ this.wait = true;
617
+ }
618
+
619
+ // once we've set our items check if we need to auto-select
620
+ if (selectFirst && item.items.length > 0) {
621
+ this.handleItemClicked(null, item.items[0]);
622
+ }
623
+
656
624
  this.requestUpdate('root');
657
625
  this.scrollSelectedIntoView();
658
- if (this.pending && this.pending.length > 0) {
659
- // auto select the next pending click
660
- const nextId = this.pending.shift();
661
- if (nextId && items.length > 0) {
662
- const nextItem = findItem(items, nextId);
663
- if (nextItem.item) {
664
- this.handleItemClicked(null, nextItem.item);
665
- } else {
666
- this.fireNoPath(nextId);
667
- }
668
- }
669
- } else {
670
- // auto select the first item
671
- if (
672
- selectFirst &&
673
- items.length > 0 &&
674
- this.selection.length >= 1 &&
675
- !item.inline
676
- ) {
677
- for (const item of items) {
678
- if (!item.type) {
679
- this.handleItemClicked(null, item);
680
- break;
681
- }
682
- }
683
- }
684
- }
685
626
  }
686
627
  );
687
628
  }
@@ -692,15 +633,27 @@ export class TembaMenu extends RapidElement {
692
633
  menuItem: MenuItem,
693
634
  parent: MenuItem = null
694
635
  ) {
695
- this.fireCustomEvent(CustomEventType.ButtonClicked, {
696
- item: menuItem,
697
- parent,
698
- });
699
636
  if (parent && parent.popup) {
637
+ if (event) {
638
+ this.fireCustomEvent(CustomEventType.ButtonClicked, {
639
+ item: menuItem,
640
+ selection: this.getSelection(),
641
+ parent,
642
+ });
643
+ }
644
+
700
645
  return;
701
646
  }
702
647
 
703
648
  if (menuItem.popup) {
649
+ if (event) {
650
+ this.fireCustomEvent(CustomEventType.ButtonClicked, {
651
+ item: menuItem,
652
+ selection: this.getSelection(),
653
+ parent,
654
+ });
655
+ }
656
+
704
657
  return;
705
658
  }
706
659
 
@@ -717,63 +670,37 @@ export class TembaMenu extends RapidElement {
717
670
  event.stopPropagation();
718
671
  }
719
672
 
720
- if (menuItem.trigger) {
721
- new Function(menuItem.trigger)();
673
+ // update our selection
674
+ if (menuItem.level >= this.selection.length) {
675
+ this.selection.push(menuItem.vanity_id || menuItem.id);
722
676
  } else {
723
- if (menuItem.level === 0) {
724
- /* this.expanding = menuItem.id;
725
- window.setTimeout(() => {
726
- this.expanding = null;
727
- }, 60);
728
- */
729
- }
730
-
731
- // update our selection
732
- if (menuItem.level >= this.selection.length) {
733
- this.selection.push(menuItem.vanity_id || menuItem.id);
734
- } else {
735
- this.selection.splice(
736
- menuItem.level,
737
- this.selection.length - menuItem.level,
738
- menuItem.vanity_id || menuItem.id
739
- );
740
- }
677
+ this.selection.splice(
678
+ menuItem.level,
679
+ this.selection.length - menuItem.level,
680
+ menuItem.vanity_id || menuItem.id
681
+ );
682
+ }
741
683
 
742
- if (menuItem.endpoint) {
743
- this.loadItems(menuItem, this.pending.length == 0);
684
+ if (menuItem.endpoint) {
685
+ this.loadItems(menuItem, !!event);
744
686
 
745
- // make sure change events fire for events with hrefs
746
- if (!menuItem.href) {
747
- return;
748
- }
749
- } else {
750
- if (this.pending && this.pending.length > 0) {
751
- // auto select the next pending click
752
- const nextId = this.pending.shift();
753
- const item = this.getMenuItem();
754
- if (nextId && item && item.items && item.items.length > 0) {
755
- const nextItem = findItem(item.items, nextId).item;
756
- if (nextItem) {
757
- this.handleItemClicked(null, nextItem);
758
- return;
759
- } else {
760
- this.fireNoPath(nextId);
761
- this.requestUpdate('root');
762
- return;
763
- }
764
- } else {
765
- this.fireNoPath(nextId);
766
- this.requestUpdate('root');
767
- return;
768
- }
769
- }
770
- this.requestUpdate('root');
687
+ // make sure change events fire for events with hrefs
688
+ if (!menuItem.href) {
689
+ return;
771
690
  }
691
+ } else {
692
+ this.requestUpdate();
693
+ }
772
694
 
773
- if (this.pending.length == 0 || this.getMenuItem().href) {
774
- this.dispatchEvent(new Event('change'));
775
- }
695
+ if (menuItem.href) {
696
+ this.dispatchEvent(new Event('change'));
776
697
  }
698
+
699
+ this.fireCustomEvent(CustomEventType.ButtonClicked, {
700
+ item: menuItem,
701
+ selection: this.getSelection(),
702
+ parent,
703
+ });
777
704
  }
778
705
 
779
706
  public scrollSelectedIntoView() {
@@ -828,28 +755,6 @@ export class TembaMenu extends RapidElement {
828
755
  return this.selection;
829
756
  }
830
757
 
831
- public setSelection(path: string[]) {
832
- this.pending = [...path];
833
- this.selection = [];
834
-
835
- if (this.wait) {
836
- this.wait = false;
837
- this.loadItems(this.root);
838
- }
839
- }
840
-
841
- public setSelectionPath(path: string) {
842
- const asPath = path.split('/').filter(step => !!step);
843
-
844
- // first try to click in the current space
845
- const clicked = this.clickItem(asPath[asPath.length - 1]);
846
-
847
- if (!clicked) {
848
- this.wait = true;
849
- this.setSelection(asPath);
850
- }
851
- }
852
-
853
758
  public handleExpand() {
854
759
  this.collapsed = false;
855
760
  }
@@ -860,6 +765,9 @@ export class TembaMenu extends RapidElement {
860
765
 
861
766
  public async setFocusedItem(path: string) {
862
767
  const focusedPath = path.split('/').filter(step => !!step);
768
+ if (!this.root) {
769
+ return;
770
+ }
863
771
 
864
772
  // if we don't match at the first level, we are a noop
865
773
  if (focusedPath.length > 0) {
@@ -875,7 +783,7 @@ export class TembaMenu extends RapidElement {
875
783
  const nextId = focusedPath.shift();
876
784
  if (nextId) {
877
785
  if (!level.items) {
878
- this.loadItems(level, false);
786
+ this.loadItems(level);
879
787
  await this.httpComplete;
880
788
  }
881
789
 
@@ -179,6 +179,7 @@ export interface WebResponse {
179
179
  url?: string;
180
180
  headers: Headers;
181
181
  controller?: AbortController;
182
+ redirected?: boolean;
182
183
  }
183
184
 
184
185
  export const postUrl = (
@@ -213,6 +214,8 @@ export const postUrl = (
213
214
  json,
214
215
  headers: response.headers,
215
216
  status: response.status,
217
+ redirected: response.redirected,
218
+ url: response.url,
216
219
  });
217
220
  });
218
221
  })
@@ -21,6 +21,7 @@ export enum Icon {
21
21
  checkbox = 'square',
22
22
  checkbox_checked = 'check-square',
23
23
  contact = 'user-01',
24
+ contact_archived = 'archive',
24
25
  contact_blocked = 'message-x-square',
25
26
  contact_stopped = 'slash-octagon',
26
27
  contact_updated = 'user-edit',
@@ -43,6 +44,7 @@ export enum Icon {
43
44
  group = 'users-01',
44
45
  group_smart = 'atom-01',
45
46
  help = 'help-circle',
47
+ home = 'home-smile',
46
48
  inbox = 'inbox-01',
47
49
  label = 'tag-01',
48
50
  language = 'globe-01',
@@ -15,13 +15,13 @@ describe('temba-label', () => {
15
15
  });
16
16
 
17
17
  it('renders icon', async () => {
18
- const label: Label = await getLabel('Default', { icon: 'home' });
18
+ const label: Label = await getLabel('Default', { icon: 'check' });
19
19
  await assertScreenshot('label/default-icon', getClip(label));
20
20
  });
21
21
 
22
22
  it('renders shadow', async () => {
23
23
  const label: Label = await getLabel('Shadow', {
24
- icon: 'loader',
24
+ icon: 'check',
25
25
  shadow: true,
26
26
  });
27
27
  await assertScreenshot('label/shadow', getClip(label));
@@ -58,7 +58,7 @@ describe('temba-label', () => {
58
58
 
59
59
  it('renders danger', async () => {
60
60
  const label: Label = await getLabel('Danger', {
61
- icon: 'alert-triangle',
61
+ icon: 'check',
62
62
  danger: true,
63
63
  });
64
64
  await assertScreenshot('label/danger', getClip(label));
@@ -66,7 +66,7 @@ describe('temba-label', () => {
66
66
 
67
67
  it('renders custom', async () => {
68
68
  const label: Label = await getLabel('Custom Orange', {
69
- icon: 'tool',
69
+ icon: 'check',
70
70
  backgroundColor: 'rgb(240, 176, 29)',
71
71
  textColor: '#ffff',
72
72
  });
@@ -20,6 +20,10 @@ const getMenu = async (attrs: any = {}, width = 0) => {
20
20
  return menu;
21
21
  };
22
22
 
23
+ const IDX_CHOOSER = 0;
24
+ const IDX_TASKS = 1;
25
+ const IDX_SCHEDULE = 2;
26
+
23
27
  describe('temba-menu', () => {
24
28
  it('can be created', async () => {
25
29
  const list: TembaMenu = await getMenu();
@@ -29,24 +33,86 @@ describe('temba-menu', () => {
29
33
 
30
34
  it('renders with endpoint', async () => {
31
35
  const menu: TembaMenu = await getMenu({
32
- endpoint: '/test-assets/list/menu-root.json',
36
+ endpoint: '/test-assets/menu/menu-root.json',
33
37
  });
34
38
 
35
- expect(menu.root.items.length).to.equal(2);
36
- await assertScreenshot('list/menu-root', getClip(menu));
39
+ expect(menu.root.items.length).to.equal(3);
40
+ await assertScreenshot('menu/menu-root', getClip(menu));
37
41
  });
38
42
 
39
43
  it('supports submenu', async () => {
40
44
  const menu: TembaMenu = await getMenu({
41
- endpoint: '/test-assets/list/menu-root.json',
45
+ endpoint: '/test-assets/menu/menu-root.json',
46
+ });
47
+
48
+ // click our tasks
49
+ menu.getDiv('#menu-tasks').click();
50
+ await menu.httpComplete;
51
+ menu.requestUpdate();
52
+ // await menu.updateComplete;
53
+
54
+ expect(menu.root.items[IDX_TASKS].items.length).to.equal(3);
55
+ await assertScreenshot('menu/menu-submenu', getClip(menu));
56
+ });
57
+
58
+ it('sets focus', async () => {
59
+ // setting focus just shows the selection, it does
60
+ // not trigger events such as loading or dispatching
61
+
62
+ const menu: TembaMenu = await getMenu({
63
+ endpoint: '/test-assets/menu/menu-root.json',
42
64
  });
43
65
 
44
- // click our first item
66
+ // click our tasks
45
67
  menu.getDiv('#menu-tasks').click();
46
68
  await menu.httpComplete;
47
69
 
48
- expect(menu.root.items[0].items.length).to.equal(3);
70
+ // now set the focus manually
71
+ menu.setFocusedItem('schedule');
72
+
73
+ // setting focus does NOT fetch items
74
+ expect(menu.root.items[IDX_SCHEDULE].items).to.equal(undefined);
75
+
76
+ // now load the items explicitly
77
+ menu.getDiv('#menu-schedule').click();
78
+ await menu.httpComplete;
79
+ expect(menu.root.items[IDX_SCHEDULE].items.length).to.equal(3);
80
+
81
+ await assertScreenshot('menu/menu-focused-with items', getClip(menu));
82
+
83
+ menu.setFocusedItem('tasks');
84
+ await assertScreenshot('menu/menu-tasks', getClip(menu));
85
+
86
+ menu.setFocusedItem('tasks/todo');
87
+ await assertScreenshot('menu/menu-tasks-nextup', getClip(menu));
88
+ });
89
+
90
+ it('refreshes', async () => {
91
+ // the menu should refresh along the selection path without destroying state
92
+ const menu: TembaMenu = await getMenu({
93
+ endpoint: '/test-assets/menu/menu-root.json',
94
+ });
95
+
96
+ // click our tasks
97
+ menu.getDiv('#menu-tasks').click();
98
+ menu.requestUpdate();
99
+ await menu.httpComplete;
100
+ // await menu.updateComplete;
101
+
102
+ // now click on the todo
103
+ menu.getDiv('#menu-todo').click();
104
+ menu.requestUpdate();
105
+ // await menu.updateComplete;
106
+
107
+ expect(menu.root.items[IDX_TASKS].items.length).to.equal(3);
108
+ await assertScreenshot('menu/menu-refresh-1', getClip(menu));
109
+
110
+ // now refresh!
111
+ menu.refresh();
112
+ await menu.httpComplete;
49
113
 
50
- await assertScreenshot('list/menu-submenu', getClip(menu));
114
+ // we should still have our task items
115
+ expect(menu.root.items[IDX_TASKS].items.length).to.equal(3);
116
+ await assertScreenshot('menu/menu-refresh-2', getClip(menu));
51
117
  });
52
118
  });