@nyaruka/temba-components 0.171.0 → 0.172.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 +23 -1
- package/dist/temba-components.js +1015 -747
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/src/RapidElement.ts +83 -41
- package/src/display/Button.ts +14 -2
- package/src/display/Chat.ts +41 -22
- package/src/display/Label.ts +18 -3
- package/src/events/eventRenderers.ts +4 -0
- package/src/flow/FlowSearch.ts +12 -392
- package/src/flow/actions/send_msg.ts +2 -1
- package/src/flow/actions/set_contact_field.ts +1 -0
- package/src/flow/actions/set_contact_name.ts +1 -0
- package/src/flow/nodes/split_by_ticket.ts +1 -0
- package/src/flow/nodes/wait_for_dial.ts +1 -0
- package/src/form/RangePicker.ts +10 -2
- package/src/form/select/Select.ts +15 -2
- package/src/layout/SearchModal.ts +564 -0
- package/src/list/BroadcastList.ts +17 -6
- package/src/list/ContentList.ts +29 -13
- package/src/list/FieldList.ts +8 -1
- package/src/list/TembaMenu.ts +71 -13
- package/src/live/CampaignEvents.ts +31 -11
- package/src/live/ContactChat.ts +276 -76
- package/src/live/ContactTimeline.ts +46 -24
- package/src/live/ContactWatch.ts +166 -63
- package/src/live/Realtime.ts +137 -8
- package/src/live/SocketService.ts +115 -10
- package/src/live/TicketSearch.ts +290 -0
- package/src/live/Watchers.ts +93 -0
- package/src/simulator/Simulator.ts +59 -36
- package/src/store/Store.ts +17 -34
- package/src/styles/designTokens.ts +57 -17
- package/src/styles/pillVariants.ts +42 -10
- package/static/css/design-system.css +47 -12
- package/static/css/temba-components.css +31 -9
- package/temba-modules.ts +2 -0
package/src/list/TembaMenu.ts
CHANGED
|
@@ -41,6 +41,10 @@ interface MenuItemState {
|
|
|
41
41
|
collapsed?: string;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// fields the menu maintains on an item rather than reading off the server, so
|
|
45
|
+
// a reload carries them across instead of treating them as dropped
|
|
46
|
+
const OWN_ITEM_FIELDS = ['items', 'level', 'loading'];
|
|
47
|
+
|
|
44
48
|
const findItem = (
|
|
45
49
|
items: MenuItem[],
|
|
46
50
|
id: string
|
|
@@ -738,8 +742,17 @@ export class TembaMenu extends ResizeElement {
|
|
|
738
742
|
@property({ type: Object })
|
|
739
743
|
pressedItem: MenuItem;
|
|
740
744
|
|
|
741
|
-
|
|
742
|
-
|
|
745
|
+
/**
|
|
746
|
+
* Resolves when every load in flight has settled. Awaiting it has to mean
|
|
747
|
+
* "the menu has finished fetching", not "the last load anyone started has
|
|
748
|
+
* finished" - loads overlap (a click while a debounced refresh is armed),
|
|
749
|
+
* and a caller that awaited whichever promise happened to be here last
|
|
750
|
+
* would read the menu mid-update.
|
|
751
|
+
*/
|
|
752
|
+
public httpComplete: Promise<void> = Promise.resolve();
|
|
753
|
+
|
|
754
|
+
private loadsInFlight = 0;
|
|
755
|
+
private loadsSettled: () => void;
|
|
743
756
|
|
|
744
757
|
root: MenuItem;
|
|
745
758
|
selection: string[] = [];
|
|
@@ -854,22 +867,66 @@ export class TembaMenu extends ResizeElement {
|
|
|
854
867
|
});
|
|
855
868
|
}
|
|
856
869
|
|
|
870
|
+
/**
|
|
871
|
+
* Folds a load into httpComplete, which stays unresolved until the last one
|
|
872
|
+
* outstanding settles.
|
|
873
|
+
*/
|
|
874
|
+
private trackLoad(load: Promise<void>): void {
|
|
875
|
+
if (this.loadsInFlight === 0) {
|
|
876
|
+
this.httpComplete = new Promise((resolve) => {
|
|
877
|
+
this.loadsSettled = resolve;
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
this.loadsInFlight++;
|
|
881
|
+
|
|
882
|
+
load.then(() => {
|
|
883
|
+
if (--this.loadsInFlight === 0) {
|
|
884
|
+
this.loadsSettled();
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Reloading a level reuses the item objects it already had. Replacing them
|
|
891
|
+
* would orphan anything already pointing at one - a rendered click handler,
|
|
892
|
+
* or a load in flight for that item's own children, which would then land
|
|
893
|
+
* on a copy no longer in the tree.
|
|
894
|
+
*/
|
|
895
|
+
private mergeItems(item: MenuItem, items: MenuItem[]): MenuItem[] {
|
|
896
|
+
const previous = item.items || [];
|
|
897
|
+
return items.map((newItem) => {
|
|
898
|
+
const prevItem = previous.find((prev) => prev.id == newItem.id);
|
|
899
|
+
if (!prevItem) {
|
|
900
|
+
return newItem;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// sub-items the reload didn't speak to are ours to carry over - either
|
|
904
|
+
// ones we already had or ones still on their way
|
|
905
|
+
const carried = newItem.items || prevItem.items;
|
|
906
|
+
|
|
907
|
+
// anything the reload dropped is gone, so this reads as a replacement
|
|
908
|
+
// everywhere except identity - bar the fields the menu sets on an item
|
|
909
|
+
// itself, which the server never speaks to
|
|
910
|
+
Object.keys(prevItem).forEach((key) => {
|
|
911
|
+
if (!(key in newItem) && !OWN_ITEM_FIELDS.includes(key)) {
|
|
912
|
+
delete prevItem[key];
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
Object.assign(prevItem, newItem);
|
|
916
|
+
if (carried) {
|
|
917
|
+
prevItem.items = carried;
|
|
918
|
+
}
|
|
919
|
+
return prevItem;
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
|
|
857
923
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
858
924
|
private loadItems(item: MenuItem, event: MouseEvent | KeyboardEvent = null) {
|
|
859
925
|
if (item && item.endpoint) {
|
|
860
926
|
item.loading = true;
|
|
861
|
-
|
|
927
|
+
const load = fetchResults(item.endpoint)
|
|
862
928
|
.then((items: MenuItem[]) => {
|
|
863
|
-
items.
|
|
864
|
-
if (!newItem.items) {
|
|
865
|
-
const prevItem = (item.items || []).find(
|
|
866
|
-
(prev) => prev.id == newItem.id
|
|
867
|
-
);
|
|
868
|
-
if (prevItem && prevItem.items) {
|
|
869
|
-
newItem.items = prevItem.items;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
});
|
|
929
|
+
items = this.mergeItems(item, items);
|
|
873
930
|
|
|
874
931
|
// update our item level
|
|
875
932
|
items.forEach((subItem) => {
|
|
@@ -901,6 +958,7 @@ export class TembaMenu extends ResizeElement {
|
|
|
901
958
|
.catch((error) => {
|
|
902
959
|
this.fireCustomEvent(CustomEventType.Error, { error });
|
|
903
960
|
});
|
|
961
|
+
this.trackLoad(load);
|
|
904
962
|
}
|
|
905
963
|
}
|
|
906
964
|
|
|
@@ -273,11 +273,20 @@ export class CampaignEvents extends EndpointMonitorElement {
|
|
|
273
273
|
justify-content: center;
|
|
274
274
|
/* icons inside the dot take the dot's hue */
|
|
275
275
|
--icon-color: var(--dot-color);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
276
|
+
/* plain widget bg for browsers without color-mix(); the tinted
|
|
277
|
+
fill below needs the @supports gate since var() keeps an
|
|
278
|
+
invalid declaration alive until computed-value time */
|
|
279
|
+
background: var(--color-widget-bg, #fff);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@supports (color: color-mix(in srgb, red, red)) {
|
|
283
|
+
.dot {
|
|
284
|
+
background: color-mix(
|
|
285
|
+
in srgb,
|
|
286
|
+
var(--dot-color) 12%,
|
|
287
|
+
var(--color-widget-bg, #fff)
|
|
288
|
+
);
|
|
289
|
+
}
|
|
281
290
|
}
|
|
282
291
|
|
|
283
292
|
/* the anchor marks the field's date itself - painted with the widget
|
|
@@ -470,7 +479,8 @@ export class CampaignEvents extends EndpointMonitorElement {
|
|
|
470
479
|
|
|
471
480
|
.menu-button.destructive:hover {
|
|
472
481
|
border-color: #dc2626;
|
|
473
|
-
|
|
482
|
+
/* pre-mixed fallback for browsers without color-mix() */
|
|
483
|
+
background: #fdf2f2;
|
|
474
484
|
}
|
|
475
485
|
|
|
476
486
|
/* the event's action, contained with a leading type label. Children
|
|
@@ -481,16 +491,26 @@ export class CampaignEvents extends EndpointMonitorElement {
|
|
|
481
491
|
align-items: flex-start;
|
|
482
492
|
gap: 0.6em;
|
|
483
493
|
padding: 0.9em 1em 1em;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
var(--sunken, #f1f3f5) 45%,
|
|
487
|
-
var(--surface, #fff)
|
|
488
|
-
);
|
|
494
|
+
/* pre-mixed fallback for browsers without color-mix() */
|
|
495
|
+
background: #f9fafa;
|
|
489
496
|
border: 1px solid var(--border, #e4e7ec);
|
|
490
497
|
border-radius: var(--r);
|
|
491
498
|
line-height: 1.5;
|
|
492
499
|
}
|
|
493
500
|
|
|
501
|
+
@supports (color: color-mix(in srgb, red, red)) {
|
|
502
|
+
.menu-button.destructive:hover {
|
|
503
|
+
background: color-mix(in srgb, #dc2626 6%, var(--surface, #fff));
|
|
504
|
+
}
|
|
505
|
+
.detail-action {
|
|
506
|
+
background: color-mix(
|
|
507
|
+
in srgb,
|
|
508
|
+
var(--sunken, #f1f3f5) 45%,
|
|
509
|
+
var(--surface, #fff)
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
494
514
|
/* the close (✕) in the header's upper right - the square
|
|
495
515
|
hover-wash button treatment (the pager buttons'), sitting
|
|
496
516
|
just outboard of the action buttons */
|