@triptease/tt-navbar 0.0.17 → 0.0.18

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Webcomponent tt-navbar following open-wc recommendations",
4
4
  "license": "MIT",
5
5
  "author": "tt-navbar",
6
- "version": "0.0.17",
6
+ "version": "0.0.18",
7
7
  "type": "module",
8
8
  "main": "dist/src/index.js",
9
9
  "module": "dist/src/index.js",
package/src/TtNavbar.ts CHANGED
@@ -29,11 +29,66 @@ export class TtNavbar extends LitElement {
29
29
  @property({ type: String, attribute: 'client-key' })
30
30
  clientKey?: string;
31
31
 
32
+ @queryAll('details')
33
+ protected allDetailsElements!: Array<HTMLDetailsElement>;
34
+
35
+ @queryAll('a')
36
+ protected allNavLinks!: Array<HTMLAnchorElement>;
37
+
32
38
  @state()
33
39
  private sidebarOpen = true;
34
40
 
35
- @queryAll('details')
36
- protected allDetailsElements!: Array<HTMLDetailsElement>;
41
+ protected firstUpdated() {
42
+ this.setActiveState();
43
+ }
44
+
45
+ /*
46
+ * Set the active state for the current page.
47
+ *
48
+ * This function iterates over all nav links and compares the current URL path with the href of each link.
49
+ * If the current URL path starts with the href of a link, the link is marked as active. If multiple links
50
+ * share the same prefix, the longest prefix is used as it is more specific.
51
+ *
52
+ * Example:
53
+ * - Current URL: /channels/123
54
+ * - Nav links:
55
+ * - /channels
56
+ * - /channels/123
57
+ * - /channels/456
58
+ *
59
+ * Loop 1: currentPath = /channels/123, linkPath = /channels, bestMatch = /channels
60
+ * Loop 2: currentPath = /channels/123, linkPath = /channels/123, bestMatch = /channels/123
61
+ * Loop 3: currentPath = /channels/123, linkPath = /channels/456, bestMatch = /channels/123
62
+ * Result: /channels/123 is marked as active
63
+ *
64
+ */
65
+ private setActiveState = () => {
66
+ const currentPath = window.location.pathname;
67
+
68
+ let bestMatch: HTMLAnchorElement | undefined;
69
+ let bestMatchLength = 0;
70
+
71
+ for (const link of this.allNavLinks) {
72
+ link.classList.remove('current-page');
73
+ if (link.hasAttribute('aria-current')) {
74
+ link.attributes.removeNamedItem('aria-current');
75
+ }
76
+
77
+ const linkPath = new URL(link.href).pathname;
78
+
79
+ if (currentPath.startsWith(linkPath)) {
80
+ if (linkPath.length > bestMatchLength) {
81
+ bestMatch = link;
82
+ bestMatchLength = linkPath.length;
83
+ }
84
+ }
85
+ }
86
+
87
+ if (bestMatch) {
88
+ bestMatch.classList.add('current-page');
89
+ bestMatch.setAttribute('aria-current', 'page');
90
+ }
91
+ };
37
92
 
38
93
  private closeAllDetails = (element?: HTMLDetailsElement) => {
39
94
  this.allDetailsElements.forEach(detail => {
@@ -234,4 +234,28 @@ describe('TtNavbar', () => {
234
234
  });
235
235
  });
236
236
  });
237
+
238
+ const URLs = [
239
+ ['/', 'Dashboard'],
240
+ ['/channels', 'Channels'],
241
+ [`/parity/${CLIENT_KEY}`, 'Parity'],
242
+ [`/parity/${CLIENT_KEY}`, 'Parity'],
243
+ [`/parity/${CLIENT_KEY}/foo`, 'Parity'],
244
+ ];
245
+
246
+ URLs.forEach(([route, text]) => {
247
+ it(`should show the current page as active when link is ${route}`, async () => {
248
+ // eslint-disable-next-line no-restricted-globals
249
+ history.pushState({}, '', route); // 👈 mock URL
250
+
251
+ const navbar = await fixture<TtNavbar>(
252
+ `<tt-navbar client-key=${CLIENT_KEY}></tt-navbar>`,
253
+ );
254
+
255
+ const anchor = navbar.shadowRoot!.querySelector('a[aria-current="page"]');
256
+
257
+ expect(anchor).to.exist;
258
+ expect(anchor!.textContent).to.include(text);
259
+ });
260
+ });
237
261
  });