@nyaruka/temba-components 0.164.0 → 0.165.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 (49) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/DEV_DATA.md +11 -11
  3. package/README.md +4 -4
  4. package/TEST_OPTIMIZATION.md +8 -11
  5. package/dist/locales/es.js +4 -0
  6. package/dist/locales/es.js.map +1 -1
  7. package/dist/locales/fr.js +4 -0
  8. package/dist/locales/fr.js.map +1 -1
  9. package/dist/locales/pt.js +4 -0
  10. package/dist/locales/pt.js.map +1 -1
  11. package/dist/static/svg/index.svg +1 -1
  12. package/dist/temba-components.js +1555 -484
  13. package/dist/temba-components.js.map +1 -1
  14. package/orca/setup.sh +2 -2
  15. package/package.json +13 -18
  16. package/scripts/dev-data-sync.mjs +4 -4
  17. package/src/Icons.ts +3 -2
  18. package/src/display/ProgressBar.ts +15 -2
  19. package/src/form/Compose.ts +31 -1
  20. package/src/form/ContactSearch.ts +229 -126
  21. package/src/interfaces.ts +52 -0
  22. package/src/layout/Card.ts +25 -0
  23. package/src/layout/CardLayout.ts +3 -3
  24. package/src/layout/HeaderBar.ts +4 -1
  25. package/src/list/BroadcastList.ts +912 -0
  26. package/src/list/ContentList.ts +46 -10
  27. package/src/list/FieldList.ts +1057 -0
  28. package/src/list/SortableList.ts +7 -2
  29. package/src/list/TembaList.ts +8 -0
  30. package/src/list/TembaMenu.ts +5 -38
  31. package/src/live/CampaignEvents.ts +33 -19
  32. package/src/live/ContactChat.ts +7 -1
  33. package/src/live/ContactDetails.ts +20 -13
  34. package/src/live/ContactFieldEditor.ts +7 -3
  35. package/src/live/ContactStoreElement.ts +3 -1
  36. package/src/live/ContactTimeline.ts +28 -7
  37. package/src/locales/es.ts +4 -0
  38. package/src/locales/fr.ts +4 -0
  39. package/src/locales/pt.ts +4 -0
  40. package/static/svg/index.svg +1 -1
  41. package/static/svg/packs/2-temba/star-filled.svg +3 -0
  42. package/static/svg/work/traced/star-filled.svg +1 -0
  43. package/static/svg/work/used/star-filled.svg +3 -0
  44. package/stress-test.js +3 -3
  45. package/temba-modules.ts +4 -0
  46. package/web-test-runner.config.mjs +1 -1
  47. package/xliff/es.xlf +12 -0
  48. package/xliff/fr.xlf +12 -0
  49. package/xliff/pt.xlf +12 -0
@@ -1151,12 +1151,19 @@ export class ContentList<T = any> extends RapidElement {
1151
1151
  * page-header attribute to this (rather than the raw endpoint) makes
1152
1152
  * the menu re-fetch whenever the committed search changes. */
1153
1153
  private contentMenuEndpointWithSearch(): string {
1154
- if (!this.contentMenuEndpoint || !this.search) {
1154
+ if (!this.contentMenuEndpoint) {
1155
1155
  return this.contentMenuEndpoint;
1156
1156
  }
1157
1157
  try {
1158
1158
  const url = new URL(this.contentMenuEndpoint, window.location.origin);
1159
- url.searchParams.set('search', this.search);
1159
+ // With no committed search, *strip* any search param rather than
1160
+ // passing the endpoint through — hosts typically bake the
1161
+ // original request's query string into the attribute (so a
1162
+ // deep-linked search seeds the menu), and clearing the search in
1163
+ // the list must also clear it from the menu fetch or
1164
+ // search-dependent items (e.g. "Create Smart Group") linger.
1165
+ if (this.search) url.searchParams.set('search', this.search);
1166
+ else url.searchParams.delete('search');
1160
1167
  return url.pathname + url.search;
1161
1168
  } catch {
1162
1169
  return this.contentMenuEndpoint;
@@ -1589,6 +1596,20 @@ export class ContentList<T = any> extends RapidElement {
1589
1596
  private writeUrlState(replace = false): void {
1590
1597
  this.bubbleHistoryState(replace);
1591
1598
  if (!this.urlState) return;
1599
+ const url = this.buildBrowserUrl();
1600
+ if (replace) {
1601
+ window.history.replaceState({}, '', url);
1602
+ } else {
1603
+ window.history.pushState({}, '', url);
1604
+ }
1605
+ }
1606
+
1607
+ /** The address-bar URL reflecting the current list state: the
1608
+ * page's path with the list's search/sort/page params folded into
1609
+ * the existing query string (set when active, removed when not —
1610
+ * so a cleared search drops its param). Params the list doesn't
1611
+ * own are preserved. */
1612
+ private buildBrowserUrl(): string {
1592
1613
  const params = new URLSearchParams(window.location.search);
1593
1614
  const k = (name: string) =>
1594
1615
  this.urlParamPrefix ? `${this.urlParamPrefix}_${name}` : name;
@@ -1602,12 +1623,7 @@ export class ContentList<T = any> extends RapidElement {
1602
1623
  setOrDelete(k('page'), this.page > 1 ? String(this.page) : '');
1603
1624
 
1604
1625
  const qs = params.toString();
1605
- const url = window.location.pathname + (qs ? '?' + qs : '');
1606
- if (replace) {
1607
- window.history.replaceState({}, '', url);
1608
- } else {
1609
- window.history.pushState({}, '', url);
1610
- }
1626
+ return window.location.pathname + (qs ? '?' + qs : '');
1611
1627
  }
1612
1628
 
1613
1629
  /** Read saved list state out of the host's history entry under
@@ -1620,7 +1636,19 @@ export class ContentList<T = any> extends RapidElement {
1620
1636
  private readHistoryState(): void {
1621
1637
  const key = this.historyStateKey;
1622
1638
  if (!key) return;
1623
- const stash = (window.history.state || {})[key] || {};
1639
+ const state = window.history.state || {};
1640
+ // A fresh navigation has no stash for this list yet, but the link
1641
+ // itself may deep-link a query (e.g. /contact/?search=age%3E10) —
1642
+ // fall back to the URL params so the list initializes on them.
1643
+ // Once the list has bubbled state for this entry the stash exists
1644
+ // (even with an empty search) and takes precedence, so a cleared
1645
+ // search isn't resurrected by a stale query string.
1646
+ if (!(key in state)) {
1647
+ this.restoreUrl = '';
1648
+ this.readUrlState();
1649
+ return;
1650
+ }
1651
+ const stash = state[key] || {};
1624
1652
  const previousSearch = this.search;
1625
1653
  this.search = typeof stash.search === 'string' ? stash.search : '';
1626
1654
  this.sort = typeof stash.sort === 'string' ? stash.sort : '';
@@ -1670,10 +1698,18 @@ export class ContentList<T = any> extends RapidElement {
1670
1698
  if (this.cursorMode && this.currentUrl) {
1671
1699
  state.url = this.currentUrl;
1672
1700
  }
1701
+ // `url` (distinct from `state.url`, the fetch cursor) is the
1702
+ // address-bar URL for this list state — the host should pass it
1703
+ // as the url argument of its pushState/replaceState so a
1704
+ // committed or cleared search is reflected in the address bar,
1705
+ // while keeping its own stashed page URL (frame.js's
1706
+ // `state.url`) stable so in-list back/forward isn't mistaken
1707
+ // for a cross-page navigation.
1673
1708
  this.fireCustomEvent(CustomEventType.HistoryChange, {
1674
1709
  key: this.historyStateKey,
1675
1710
  state,
1676
- replace
1711
+ replace,
1712
+ url: this.buildBrowserUrl()
1677
1713
  });
1678
1714
  }
1679
1715