@countermeasure-platform/web-components 1.3.3-dev.33.1 → 1.3.4

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 (64) hide show
  1. package/README.md +31 -0
  2. package/dist/component-D5sRm1fq.js +389 -0
  3. package/dist/component-D5sRm1fq.js.map +1 -0
  4. package/dist/components/index.js +27 -27
  5. package/dist/icons/index.d.ts +12 -0
  6. package/dist/icons/index.d.ts.map +1 -1
  7. package/dist/icons/index.js +12 -0
  8. package/dist/icons/index.js.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +127 -126
  12. package/dist/layout/app-shell.d.ts +50 -3
  13. package/dist/layout/app-shell.d.ts.map +1 -1
  14. package/dist/layout/app-shell.js +142 -13
  15. package/dist/layout/app-shell.js.map +1 -1
  16. package/dist/layout/core-app-chrome.d.ts +81 -0
  17. package/dist/layout/core-app-chrome.d.ts.map +1 -0
  18. package/dist/layout/core-app-chrome.js +349 -0
  19. package/dist/layout/core-app-chrome.js.map +1 -0
  20. package/dist/layout/index.d.ts +4 -2
  21. package/dist/layout/index.d.ts.map +1 -1
  22. package/dist/layout/index.js +88 -87
  23. package/dist/layout/index.js.map +1 -1
  24. package/dist/react/primitives/badge.d.ts +1 -1
  25. package/dist/react/primitives/button.d.ts +2 -2
  26. package/dist/react/primitives/copy-button.d.ts +1 -1
  27. package/dist/react/primitives/stat-card.d.ts +1 -1
  28. package/dist/react/primitives/toggle.d.ts +1 -1
  29. package/dist/react/sidebar.d.ts +4 -4
  30. package/dist/react/sidebar.d.ts.map +1 -1
  31. package/dist/react/sidebar.js +18 -16
  32. package/dist/react/sidebar.js.map +1 -1
  33. package/dist/sidebar/component.d.ts +24 -2
  34. package/dist/sidebar/component.d.ts.map +1 -1
  35. package/dist/sidebar/enhance.d.ts +8 -0
  36. package/dist/sidebar/enhance.d.ts.map +1 -1
  37. package/dist/sidebar/index.d.ts +3 -0
  38. package/dist/sidebar/index.d.ts.map +1 -1
  39. package/dist/sidebar/index.js +81 -28
  40. package/dist/sidebar/index.js.map +1 -1
  41. package/dist/sidebar/types.d.ts +126 -4
  42. package/dist/sidebar/types.d.ts.map +1 -1
  43. package/dist/styles/layout.css +252 -0
  44. package/dist/styles/sidebar.css +313 -5
  45. package/package.json +6 -1
  46. package/src/icons/icons.test.ts +9 -0
  47. package/src/icons/index.ts +12 -0
  48. package/src/index.ts +11 -0
  49. package/src/layout/app-shell.test.ts +204 -0
  50. package/src/layout/app-shell.ts +362 -3
  51. package/src/layout/core-app-chrome.test.ts +507 -0
  52. package/src/layout/core-app-chrome.ts +662 -0
  53. package/src/layout/index.ts +36 -2
  54. package/src/react/sidebar.test.tsx +104 -3
  55. package/src/react/sidebar.tsx +26 -4
  56. package/src/sidebar/component.test.ts +395 -1
  57. package/src/sidebar/component.ts +661 -86
  58. package/src/sidebar/enhance.ts +118 -0
  59. package/src/sidebar/index.ts +144 -0
  60. package/src/sidebar/types.ts +143 -4
  61. package/src/styles/layout.css +252 -0
  62. package/src/styles/sidebar.css +313 -5
  63. package/dist/component-Bxhxf21c.js +0 -167
  64. package/dist/component-Bxhxf21c.js.map +0 -1
@@ -1,66 +1,67 @@
1
1
  import { n as e } from "../sanitize-uWpY18N9.js";
2
2
  import { t } from "../navigation-DihI8dfX.js";
3
- import { AppShell as n, createAppShell as r } from "./app-shell.js";
4
- import { PageHeader as i, createPageHeader as a } from "./page-header.js";
3
+ import { AppShell as n, TopMenuBar as r, createAppShell as i, createProductShell as a, createTopMenuBar as o } from "./app-shell.js";
4
+ import { CORE_APP_ROUTE_LABELS as s, createCoreAppChromePreset as c, createCoreAppSidebarConfig as l, createCoreAppSidebarSections as u, createCoreAppTopbarActions as d, createCoreAppTopbarConfig as f, getCoreAppRouteLabel as p, mountCoreAppChrome as m, mountCoreAppChromeFromDom as h } from "./core-app-chrome.js";
5
+ import { PageHeader as g, createPageHeader as _ } from "./page-header.js";
5
6
  //#region src/layout/index.ts
6
- var o = () => {
7
+ var v = () => {
7
8
  if (typeof window > "u") return null;
8
9
  try {
9
10
  return window.localStorage;
10
11
  } catch {
11
12
  return console.warn("Unable to access localStorage"), null;
12
13
  }
13
- }, s = (e) => {
14
+ }, y = (e) => {
14
15
  try {
15
16
  return new URL(e, window.location.href).origin === window.location.origin;
16
17
  } catch {
17
18
  return !1;
18
19
  }
19
- }, c = {
20
+ }, b = {
20
21
  storageKey: "cmm-sidebar-state",
21
22
  desktopBreakpoint: "(min-width: 1024px)",
22
23
  collapsedClass: "cmm-shell--sidebar-collapsed",
23
24
  openClass: "cmm-shell--sidebar-open"
24
25
  };
25
- function l(e = {}) {
26
+ function x(e = {}) {
26
27
  let t = {
27
- ...c,
28
+ ...b,
28
29
  ...e
29
30
  }, n = document.querySelector("[data-shell-layout]"), r = document.querySelector("[data-shell-sidebar]");
30
31
  if (!n || !r) return () => {};
31
- let i = o(), a = window.matchMedia(t.desktopBreakpoint), s = Array.from(document.querySelectorAll("[data-sidebar-toggle]")), l = Array.from(document.querySelectorAll("[data-sidebar-dismiss]")), u = r.id || "shell-sidebar";
32
- r.id = u;
33
- let d = () => i?.getItem(t.storageKey) === "collapsed", f = () => {
32
+ let i = v(), a = window.matchMedia(t.desktopBreakpoint), o = Array.from(document.querySelectorAll("[data-sidebar-toggle]")), s = Array.from(document.querySelectorAll("[data-sidebar-dismiss]")), c = r.id || "shell-sidebar";
33
+ r.id = c;
34
+ let l = () => i?.getItem(t.storageKey) === "collapsed", u = () => {
34
35
  let e = a.matches ? !n.classList.contains(t.collapsedClass) : n.classList.contains(t.openClass);
35
- s.forEach((t) => {
36
- t.setAttribute("aria-expanded", e.toString()), t.setAttribute("aria-controls", u);
36
+ o.forEach((t) => {
37
+ t.setAttribute("aria-expanded", e.toString()), t.setAttribute("aria-controls", c);
37
38
  });
38
- }, p = (e, r = !0) => {
39
- n.classList.toggle(t.collapsedClass, e), r && a.matches && i?.setItem(t.storageKey, e ? "collapsed" : "expanded"), f();
40
- }, m = (e) => {
41
- n.classList.toggle(t.openClass, e), f();
39
+ }, d = (e, r = !0) => {
40
+ n.classList.toggle(t.collapsedClass, e), r && a.matches && i?.setItem(t.storageKey, e ? "collapsed" : "expanded"), u();
41
+ }, f = (e) => {
42
+ n.classList.toggle(t.openClass, e), u();
43
+ }, p = () => {
44
+ a.matches ? (f(!1), d(l(), !1)) : (f(!1), d(!1, !1));
45
+ }, m = () => {
46
+ a.matches ? d(!n.classList.contains(t.collapsedClass)) : f(!n.classList.contains(t.openClass));
42
47
  }, h = () => {
43
- a.matches ? (m(!1), p(d(), !1)) : (m(!1), p(!1, !1));
44
- }, g = () => {
45
- a.matches ? p(!n.classList.contains(t.collapsedClass)) : m(!n.classList.contains(t.openClass));
46
- }, _ = () => {
47
- m(!1);
48
- }, v = (e) => {
49
- e.key === "Escape" && !a.matches && m(!1);
48
+ f(!1);
49
+ }, g = (e) => {
50
+ e.key === "Escape" && !a.matches && f(!1);
50
51
  };
51
- return s.forEach((e) => {
52
- e.addEventListener("click", g);
53
- }), l.forEach((e) => {
54
- e.addEventListener("click", _);
55
- }), document.addEventListener("keydown", v), a.addEventListener("change", h), h(), () => {
56
- s.forEach((e) => {
57
- e.removeEventListener("click", g);
58
- }), l.forEach((e) => {
59
- e.removeEventListener("click", _);
60
- }), document.removeEventListener("keydown", v), a.removeEventListener("change", h);
52
+ return o.forEach((e) => {
53
+ e.addEventListener("click", m);
54
+ }), s.forEach((e) => {
55
+ e.addEventListener("click", h);
56
+ }), document.addEventListener("keydown", g), a.addEventListener("change", p), p(), () => {
57
+ o.forEach((e) => {
58
+ e.removeEventListener("click", m);
59
+ }), s.forEach((e) => {
60
+ e.removeEventListener("click", h);
61
+ }), document.removeEventListener("keydown", g), a.removeEventListener("change", p);
61
62
  };
62
63
  }
63
- function u(e = {}) {
64
+ function S(e = {}) {
64
65
  let t = e.triggerSelector ?? "[data-user-trigger]", n = e.dropdownSelector ?? "[data-user-dropdown]", r = document.querySelector("[data-user-menu]"), i = document.querySelector(t), a = document.querySelector(n);
65
66
  if (!r || !i || !a) return () => {};
66
67
  let o = (e) => {
@@ -83,17 +84,17 @@ function u(e = {}) {
83
84
  i.removeEventListener("click", c), document.removeEventListener("click", l), document.removeEventListener("keydown", u), a.removeEventListener("keydown", f), i.removeEventListener("keydown", p);
84
85
  };
85
86
  }
86
- var d = (e) => e.dataset.collapsibleOpen === "true" || !e.classList.contains("hidden"), f = "cmm-collapsible--hidden", p = (e, t) => {
87
- e.classList.toggle("hidden", !t), e.classList.toggle(f, !t), e.dataset.collapsibleOpen = t.toString(), e.setAttribute("aria-hidden", t ? "false" : "true");
88
- }, m = (e, t) => {
87
+ var C = (e) => e.dataset.collapsibleOpen === "true" || !e.classList.contains("hidden"), w = "cmm-collapsible--hidden", T = (e, t) => {
88
+ e.classList.toggle("hidden", !t), e.classList.toggle(w, !t), e.dataset.collapsibleOpen = t.toString(), e.setAttribute("aria-hidden", t ? "false" : "true");
89
+ }, E = (e, t) => {
89
90
  document.querySelectorAll(`[data-toggle-target='${e}']`).forEach((e) => {
90
91
  e.setAttribute("aria-expanded", t.toString());
91
92
  });
92
93
  };
93
- function h(e = {}) {
94
+ function D(e = {}) {
94
95
  let { scrollIntoView: t = !0, scrollBehavior: n = "smooth", hiddenClass: r = "cmm-collapsible--hidden" } = e;
95
- f = r, document.querySelectorAll("[data-collapsible]").forEach((e) => {
96
- p(e, e.dataset.collapsibleOpen === "true" || !e.classList.contains("hidden"));
96
+ w = r, document.querySelectorAll("[data-collapsible]").forEach((e) => {
97
+ T(e, e.dataset.collapsibleOpen === "true" || !e.classList.contains("hidden"));
97
98
  });
98
99
  let i = [];
99
100
  document.querySelectorAll("[data-toggle-target]").forEach((e) => {
@@ -102,11 +103,11 @@ function h(e = {}) {
102
103
  let a = document.querySelector(r);
103
104
  if (!a) return;
104
105
  let o = a.id || r.replace(/^#/, "");
105
- e.setAttribute("aria-controls", o), e.setAttribute("aria-expanded", d(a).toString());
106
+ e.setAttribute("aria-controls", o), e.setAttribute("aria-expanded", C(a).toString());
106
107
  let s = (e) => {
107
108
  e.preventDefault();
108
- let i = !d(a);
109
- p(a, i), m(r, i), i && t && a.scrollIntoView({
109
+ let i = !C(a);
110
+ T(a, i), E(r, i), i && t && a.scrollIntoView({
110
111
  behavior: n,
111
112
  block: "start"
112
113
  });
@@ -121,7 +122,7 @@ function h(e = {}) {
121
122
  let n = document.querySelector(t);
122
123
  if (!n) return;
123
124
  let r = (e) => {
124
- e.preventDefault(), p(n, !1), m(t, !1);
125
+ e.preventDefault(), T(n, !1), E(t, !1);
125
126
  };
126
127
  e.addEventListener("click", r), i.push({
127
128
  element: e,
@@ -131,7 +132,7 @@ function h(e = {}) {
131
132
  let a = window.location.hash;
132
133
  if (a) {
133
134
  let e = document.querySelector(a);
134
- e?.dataset.collapsible !== void 0 && (p(e, !0), m(a, !0));
135
+ e?.dataset.collapsible !== void 0 && (T(e, !0), E(a, !0));
135
136
  }
136
137
  return () => {
137
138
  i.forEach(({ element: e, handler: t }) => {
@@ -139,17 +140,17 @@ function h(e = {}) {
139
140
  });
140
141
  };
141
142
  }
142
- var g = "\n <div class=\"cmm-sheet__loading\">\n <div class=\"cmm-spinner\"></div>\n </div>\n", _ = "\n <div class=\"cmm-sheet__error\">\n <p>Failed to load content. Please try the full page.</p>\n </div>\n";
143
- function v(t, n) {
143
+ var O = "\n <div class=\"cmm-sheet__loading\">\n <div class=\"cmm-spinner\"></div>\n </div>\n", k = "\n <div class=\"cmm-sheet__error\">\n <p>Failed to load content. Please try the full page.</p>\n </div>\n";
144
+ function A(t, n) {
144
145
  t.replaceChildren(e(n, { allowSvg: !0 }));
145
146
  }
146
- function y(e = {}) {
147
- let { openClass: n = "cmm-sheet--open", hiddenClass: r = "cmm-sheet--hidden", backdropVisibleClass: i = "cmm-sheet__backdrop--visible", backdropHiddenClass: a = "cmm-sheet__backdrop--hidden", spinnerHtml: o = g, errorHtml: c = _, allowCrossOriginFetch: l = !1 } = e, u = /* @__PURE__ */ new Map();
147
+ function j(e = {}) {
148
+ let { openClass: n = "cmm-sheet--open", hiddenClass: r = "cmm-sheet--hidden", backdropVisibleClass: i = "cmm-sheet__backdrop--visible", backdropHiddenClass: a = "cmm-sheet__backdrop--hidden", spinnerHtml: o = O, errorHtml: s = k, allowCrossOriginFetch: c = !1 } = e, l = /* @__PURE__ */ new Map();
148
149
  if (document.querySelectorAll("[data-sheet]").forEach((e) => {
149
150
  let t = e.dataset.sheet;
150
151
  if (!t) return;
151
152
  let n = document.querySelector(`[data-sheet-backdrop='${t}']`), r = e.querySelector("[data-sheet-content]");
152
- !n || !r || u.set(t, {
153
+ !n || !r || l.set(t, {
153
154
  key: t,
154
155
  sheet: e,
155
156
  backdrop: n,
@@ -158,46 +159,46 @@ function y(e = {}) {
158
159
  expandLink: e.querySelector("[data-sheet-expand]"),
159
160
  focusSelector: e.dataset.sheetFocus
160
161
  });
161
- }), u.size === 0) return () => {};
162
- let d = null, f = (e, t) => {
162
+ }), l.size === 0) return () => {};
163
+ let u = null, d = (e, t) => {
163
164
  e.sheet.classList.toggle(n, t), e.sheet.classList.toggle(r, !t), e.backdrop.classList.toggle(i, t), e.backdrop.classList.toggle(a, !t), e.sheet.setAttribute("aria-hidden", t ? "false" : "true");
164
- }, p = (e) => {
165
- d?.key === e.key && (d = null), f(e, !1);
166
- }, m = async (e, n) => {
167
- d && d.key !== e.key && p(d), d = e, f(e, !0);
168
- let r = n?.dataset.sheetUrl ?? e.sheet.dataset.sheetUrl, i = n?.dataset.sheetFocus ?? e.focusSelector, a = n?.dataset.sheetFullpage ?? e.sheet.dataset.sheetFullpage, u = n?.dataset.sheetExpand ?? e.sheet.dataset.sheetExpand;
169
- if (e.fullPageLink && a && (e.fullPageLink.href = a), e.expandLink && u && (e.expandLink.href = u), !r || !l && !s(r)) {
170
- v(e.content, c);
165
+ }, f = (e) => {
166
+ u?.key === e.key && (u = null), d(e, !1);
167
+ }, p = async (e, n) => {
168
+ u && u.key !== e.key && f(u), u = e, d(e, !0);
169
+ let r = n?.dataset.sheetUrl ?? e.sheet.dataset.sheetUrl, i = n?.dataset.sheetFocus ?? e.focusSelector, a = n?.dataset.sheetFullpage ?? e.sheet.dataset.sheetFullpage, l = n?.dataset.sheetExpand ?? e.sheet.dataset.sheetExpand;
170
+ if (e.fullPageLink && a && (e.fullPageLink.href = a), e.expandLink && l && (e.expandLink.href = l), !r || !c && !y(r)) {
171
+ A(e.content, s);
171
172
  return;
172
173
  }
173
- v(e.content, o);
174
+ A(e.content, o);
174
175
  try {
175
176
  let n = await fetch(r);
176
177
  if (n.ok) {
177
178
  let t = await n.text();
178
- v(e.content, t), i && e.content.querySelector(i)?.focus();
179
- } else a ? t(a) : v(e.content, c);
179
+ A(e.content, t), i && e.content.querySelector(i)?.focus();
180
+ } else a ? t(a) : A(e.content, s);
180
181
  } catch {
181
- a ? t(a) : v(e.content, c);
182
+ a ? t(a) : A(e.content, s);
182
183
  }
183
- }, h = [];
184
- u.forEach((e) => {
184
+ }, m = [];
185
+ l.forEach((e) => {
185
186
  let t = (t) => {
186
- t.target.closest("[data-sheet-close]") && (t.preventDefault(), p(e));
187
+ t.target.closest("[data-sheet-close]") && (t.preventDefault(), f(e));
187
188
  }, n = () => {
188
- p(e);
189
+ f(e);
189
190
  }, r = () => {
190
- p(e);
191
+ f(e);
191
192
  };
192
- e.sheet.addEventListener("click", t), e.backdrop.addEventListener("click", n), h.push({
193
+ e.sheet.addEventListener("click", t), e.backdrop.addEventListener("click", n), m.push({
193
194
  element: e.sheet,
194
195
  event: "click",
195
196
  handler: t
196
- }), h.push({
197
+ }), m.push({
197
198
  element: e.backdrop,
198
199
  event: "click",
199
200
  handler: n
200
- }), e.expandLink && (e.expandLink.addEventListener("click", r), h.push({
201
+ }), e.expandLink && (e.expandLink.addEventListener("click", r), m.push({
201
202
  element: e.expandLink,
202
203
  event: "click",
203
204
  handler: r
@@ -205,27 +206,27 @@ function y(e = {}) {
205
206
  }), document.querySelectorAll("[data-sheet-trigger]").forEach((e) => {
206
207
  let t = e.dataset.sheetTrigger;
207
208
  if (!t) return;
208
- let n = u.get(t);
209
+ let n = l.get(t);
209
210
  if (!n) return;
210
211
  let r = (t) => {
211
- t.preventDefault(), m(n, e);
212
+ t.preventDefault(), p(n, e);
212
213
  };
213
- e.addEventListener("click", r), h.push({
214
+ e.addEventListener("click", r), m.push({
214
215
  element: e,
215
216
  event: "click",
216
217
  handler: r
217
218
  });
218
219
  });
219
- let y = (e) => {
220
- e.key === "Escape" && d && p(d);
220
+ let h = (e) => {
221
+ e.key === "Escape" && u && f(u);
221
222
  };
222
- return document.addEventListener("keydown", y), () => {
223
- h.forEach(({ element: e, event: t, handler: n }) => {
223
+ return document.addEventListener("keydown", h), () => {
224
+ m.forEach(({ element: e, event: t, handler: n }) => {
224
225
  e.removeEventListener(t, n);
225
- }), document.removeEventListener("keydown", y);
226
+ }), document.removeEventListener("keydown", h);
226
227
  };
227
228
  }
228
- function b() {
229
+ function M() {
229
230
  let e = document.querySelector("[data-nav-list]");
230
231
  if (!e) return () => {};
231
232
  let t = () => Array.from(e.querySelectorAll("[data-nav-item]")), n = (e) => {
@@ -236,13 +237,13 @@ function b() {
236
237
  e.removeEventListener("keydown", n);
237
238
  };
238
239
  }
239
- function x(e = {}) {
240
+ function N(e = {}) {
240
241
  let t = [
241
- l(e.sidebar),
242
- u(e.userMenu),
243
- h(e.collapsibles),
244
- y(e.sheets),
245
- b()
242
+ x(e.sidebar),
243
+ S(e.userMenu),
244
+ D(e.collapsibles),
245
+ j(e.sheets),
246
+ M()
246
247
  ];
247
248
  return () => {
248
249
  t.forEach((e) => {
@@ -251,6 +252,6 @@ function x(e = {}) {
251
252
  };
252
253
  }
253
254
  //#endregion
254
- export { n as AppShell, i as PageHeader, r as createAppShell, a as createPageHeader, x as initLayout, h as setupCollapsibles, b as setupNavKeyboard, y as setupSheets, l as setupSidebar, u as setupUserMenu };
255
+ export { n as AppShell, s as CORE_APP_ROUTE_LABELS, g as PageHeader, r as TopMenuBar, i as createAppShell, c as createCoreAppChromePreset, l as createCoreAppSidebarConfig, u as createCoreAppSidebarSections, d as createCoreAppTopbarActions, f as createCoreAppTopbarConfig, _ as createPageHeader, a as createProductShell, o as createTopMenuBar, p as getCoreAppRouteLabel, N as initLayout, m as mountCoreAppChrome, h as mountCoreAppChromeFromDom, D as setupCollapsibles, M as setupNavKeyboard, j as setupSheets, x as setupSidebar, S as setupUserMenu };
255
256
 
256
257
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/layout/index.ts"],"sourcesContent":["/**\n * Layout Components\n * @countermeasure/web-components\n *\n * Shared layout utilities for sidebar, user menu, collapsibles, and sheets.\n */\n\nimport { createSafeMarkupFragment } from '../utils/sanitize'\nimport { navigateDocumentTo } from '../utils/navigation'\nexport { AppShell, createAppShell } from './app-shell'\nexport type { AppShellConfig } from './app-shell'\nexport { PageHeader, createPageHeader } from './page-header'\nexport type { PageHeaderConfig, PageHeaderMetadata } from './page-header'\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface SidebarConfig {\n storageKey?: string\n desktopBreakpoint?: string\n collapsedClass?: string\n openClass?: string\n}\n\nexport interface UserMenuConfig {\n triggerSelector?: string\n dropdownSelector?: string\n dropdownItemSelector?: string\n}\n\nexport interface CollapsibleConfig {\n scrollIntoView?: boolean\n scrollBehavior?: ScrollBehavior\n hiddenClass?: string\n}\n\nexport interface SheetConfig {\n key: string\n sheet: HTMLElement\n backdrop: HTMLElement\n content: HTMLElement\n fullPageLink?: HTMLAnchorElement | null | undefined\n expandLink?: HTMLAnchorElement | null | undefined\n focusSelector?: string | undefined\n}\n\nexport interface SheetSetupConfig {\n openClass?: string\n hiddenClass?: string\n backdropVisibleClass?: string\n backdropHiddenClass?: string\n spinnerHtml?: string\n errorHtml?: string\n /** Allow fetching sheet content from non-same-origin URLs. Default: false. */\n allowCrossOriginFetch?: boolean\n}\n\n// ============================================================================\n// STORAGE HELPERS\n// ============================================================================\n\nconst resolveStorage = (): Storage | null => {\n if (typeof window === 'undefined') {\n return null\n }\n try {\n return window.localStorage\n } catch {\n console.warn('Unable to access localStorage')\n return null\n }\n}\n\nconst isSameOriginUrl = (url: string): boolean => {\n try {\n return new URL(url, window.location.href).origin === window.location.origin\n } catch {\n return false\n }\n}\n\n// ============================================================================\n// SIDEBAR\n// ============================================================================\n\nconst DEFAULT_SIDEBAR_CONFIG: Required<SidebarConfig> = {\n storageKey: 'cmm-sidebar-state',\n desktopBreakpoint: '(min-width: 1024px)',\n collapsedClass: 'cmm-shell--sidebar-collapsed',\n openClass: 'cmm-shell--sidebar-open',\n}\n\n/**\n * Setup responsive sidebar with collapse/expand functionality.\n */\nexport function setupSidebar(config: SidebarConfig = {}): () => void {\n const options = { ...DEFAULT_SIDEBAR_CONFIG, ...config }\n const layout = document.querySelector<HTMLElement>('[data-shell-layout]')\n const sidebar = document.querySelector<HTMLElement>('[data-shell-sidebar]')\n\n if (!layout || !sidebar) {\n return () => {\n /* noop */\n }\n }\n\n const storage = resolveStorage()\n const mediaQuery = window.matchMedia(options.desktopBreakpoint)\n const toggles = Array.from(document.querySelectorAll<HTMLElement>('[data-sidebar-toggle]'))\n const dismissors = Array.from(document.querySelectorAll<HTMLElement>('[data-sidebar-dismiss]'))\n\n const sidebarId = sidebar.id || 'shell-sidebar'\n sidebar.id = sidebarId\n\n const readStoredCollapsed = () => storage?.getItem(options.storageKey) === 'collapsed'\n\n const syncToggleAria = () => {\n const expanded = mediaQuery.matches\n ? !layout.classList.contains(options.collapsedClass)\n : layout.classList.contains(options.openClass)\n\n toggles.forEach(toggle => {\n toggle.setAttribute('aria-expanded', expanded.toString())\n toggle.setAttribute('aria-controls', sidebarId)\n })\n }\n\n const applyCollapsed = (collapsed: boolean, persist = true) => {\n layout.classList.toggle(options.collapsedClass, collapsed)\n if (persist && mediaQuery.matches) {\n storage?.setItem(options.storageKey, collapsed ? 'collapsed' : 'expanded')\n }\n syncToggleAria()\n }\n\n const setOpen = (open: boolean) => {\n layout.classList.toggle(options.openClass, open)\n syncToggleAria()\n }\n\n const syncForViewport = () => {\n if (mediaQuery.matches) {\n setOpen(false)\n applyCollapsed(readStoredCollapsed(), false)\n } else {\n setOpen(false)\n applyCollapsed(false, false)\n }\n }\n\n const handleToggleClick = () => {\n if (mediaQuery.matches) {\n const collapsed = layout.classList.contains(options.collapsedClass)\n applyCollapsed(!collapsed)\n } else {\n const open = layout.classList.contains(options.openClass)\n setOpen(!open)\n }\n }\n\n const handleDismissClick = () => {\n setOpen(false)\n }\n\n const handleKeydown = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && !mediaQuery.matches) {\n setOpen(false)\n }\n }\n\n // Attach listeners\n toggles.forEach(toggle => {\n toggle.addEventListener('click', handleToggleClick)\n })\n dismissors.forEach(dismiss => {\n dismiss.addEventListener('click', handleDismissClick)\n })\n document.addEventListener('keydown', handleKeydown)\n mediaQuery.addEventListener('change', syncForViewport)\n\n // Initialize\n syncForViewport()\n\n // Return cleanup function\n return () => {\n toggles.forEach(toggle => {\n toggle.removeEventListener('click', handleToggleClick)\n })\n dismissors.forEach(dismiss => {\n dismiss.removeEventListener('click', handleDismissClick)\n })\n document.removeEventListener('keydown', handleKeydown)\n mediaQuery.removeEventListener('change', syncForViewport)\n }\n}\n\n// ============================================================================\n// USER MENU\n// ============================================================================\n\n/**\n * Setup user menu dropdown with keyboard navigation.\n */\nexport function setupUserMenu(config: UserMenuConfig = {}): () => void {\n const triggerSelector = config.triggerSelector ?? '[data-user-trigger]'\n const dropdownSelector = config.dropdownSelector ?? '[data-user-dropdown]'\n\n const userMenu = document.querySelector<HTMLElement>('[data-user-menu]')\n const trigger = document.querySelector<HTMLButtonElement>(triggerSelector)\n const dropdown = document.querySelector<HTMLElement>(dropdownSelector)\n\n if (!userMenu || !trigger || !dropdown) {\n return () => {\n /* noop */\n }\n }\n\n const setExpanded = (expanded: boolean) => {\n trigger.setAttribute('aria-expanded', expanded.toString())\n userMenu.setAttribute('aria-expanded', expanded.toString())\n }\n\n const isExpanded = () => trigger.getAttribute('aria-expanded') === 'true'\n\n const handleTriggerClick = (event: Event) => {\n event.stopPropagation()\n setExpanded(!isExpanded())\n }\n\n const handleDocumentClick = (event: Event) => {\n if (!userMenu.contains(event.target as Node)) {\n setExpanded(false)\n }\n }\n\n const handleKeydown = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && isExpanded()) {\n setExpanded(false)\n trigger.focus()\n }\n }\n\n const dropdownItemSelector = config.dropdownItemSelector ?? '.cmm-user-menu__dropdown-item'\n\n const handleDropdownKeydown = (event: KeyboardEvent) => {\n const items = Array.from(dropdown.querySelectorAll<HTMLAnchorElement>(dropdownItemSelector))\n const currentIndex = items.indexOf(document.activeElement as HTMLAnchorElement)\n\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0\n items[nextIndex]?.focus()\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1\n items[prevIndex]?.focus()\n } else if (event.key === 'Tab' && !event.shiftKey && currentIndex === items.length - 1) {\n setExpanded(false)\n } else if (event.key === 'Tab' && event.shiftKey && currentIndex === 0) {\n setExpanded(false)\n }\n }\n\n const handleTriggerKeydown = (event: KeyboardEvent) => {\n if (\n (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') &&\n !isExpanded()\n ) {\n event.preventDefault()\n setExpanded(true)\n setTimeout(() => {\n const firstItem = dropdown.querySelector<HTMLAnchorElement>(dropdownItemSelector)\n firstItem?.focus()\n }, 50)\n }\n }\n\n // Attach listeners\n trigger.addEventListener('click', handleTriggerClick)\n document.addEventListener('click', handleDocumentClick)\n document.addEventListener('keydown', handleKeydown)\n dropdown.addEventListener('keydown', handleDropdownKeydown)\n trigger.addEventListener('keydown', handleTriggerKeydown)\n\n // Return cleanup function\n return () => {\n trigger.removeEventListener('click', handleTriggerClick)\n document.removeEventListener('click', handleDocumentClick)\n document.removeEventListener('keydown', handleKeydown)\n dropdown.removeEventListener('keydown', handleDropdownKeydown)\n trigger.removeEventListener('keydown', handleTriggerKeydown)\n }\n}\n\n// ============================================================================\n// COLLAPSIBLES\n// ============================================================================\n\nconst isCollapsibleExpanded = (section: HTMLElement) =>\n section.dataset.collapsibleOpen === 'true' || !section.classList.contains('hidden')\n\nlet collapsibleHiddenClass = 'cmm-collapsible--hidden'\n\nconst updateCollapsibleState = (section: HTMLElement, expanded: boolean) => {\n section.classList.toggle('hidden', !expanded)\n section.classList.toggle(collapsibleHiddenClass, !expanded)\n section.dataset.collapsibleOpen = expanded.toString()\n section.setAttribute('aria-hidden', expanded ? 'false' : 'true')\n}\n\nconst syncCollapsibleToggles = (selector: string, expanded: boolean) => {\n document.querySelectorAll<HTMLElement>(`[data-toggle-target='${selector}']`).forEach(toggle => {\n toggle.setAttribute('aria-expanded', expanded.toString())\n })\n}\n\n/**\n * Setup collapsible sections with toggle buttons.\n */\nexport function setupCollapsibles(config: CollapsibleConfig = {}): () => void {\n const {\n scrollIntoView = true,\n scrollBehavior = 'smooth',\n hiddenClass = 'cmm-collapsible--hidden',\n } = config\n collapsibleHiddenClass = hiddenClass\n\n // Initialize all collapsible sections\n document.querySelectorAll<HTMLElement>('[data-collapsible]').forEach(section => {\n const expanded =\n section.dataset.collapsibleOpen === 'true' || !section.classList.contains('hidden')\n updateCollapsibleState(section, expanded)\n })\n\n const handlers: { element: HTMLElement; handler: (e: Event) => void }[] = []\n\n // Setup toggle triggers\n document.querySelectorAll<HTMLElement>('[data-toggle-target]').forEach(toggle => {\n const selector = toggle.dataset.toggleTarget\n if (!selector) {\n return\n }\n\n const target = document.querySelector<HTMLElement>(selector)\n if (!target) {\n return\n }\n\n const ariaTarget = target.id || selector.replace(/^#/, '')\n toggle.setAttribute('aria-controls', ariaTarget)\n toggle.setAttribute('aria-expanded', isCollapsibleExpanded(target).toString())\n\n const handler = (event: Event) => {\n event.preventDefault()\n const expanded = !isCollapsibleExpanded(target)\n updateCollapsibleState(target, expanded)\n syncCollapsibleToggles(selector, expanded)\n\n if (expanded && scrollIntoView) {\n target.scrollIntoView({ behavior: scrollBehavior, block: 'start' })\n }\n }\n\n toggle.addEventListener('click', handler)\n handlers.push({ element: toggle, handler })\n })\n\n // Setup collapse triggers (close only)\n document.querySelectorAll<HTMLElement>('[data-collapse-target]').forEach(toggle => {\n const selector = toggle.dataset.collapseTarget\n if (!selector) {\n return\n }\n\n const target = document.querySelector<HTMLElement>(selector)\n if (!target) {\n return\n }\n\n const handler = (event: Event) => {\n event.preventDefault()\n updateCollapsibleState(target, false)\n syncCollapsibleToggles(selector, false)\n }\n\n toggle.addEventListener('click', handler)\n handlers.push({ element: toggle, handler })\n })\n\n // Handle URL hash for deep linking\n const hash = window.location.hash\n if (hash) {\n const target = document.querySelector<HTMLElement>(hash)\n if (target?.dataset.collapsible !== undefined) {\n updateCollapsibleState(target, true)\n syncCollapsibleToggles(hash, true)\n }\n }\n\n // Return cleanup function\n return () => {\n handlers.forEach(({ element, handler }) => {\n element.removeEventListener('click', handler)\n })\n }\n}\n\n// ============================================================================\n// SHEETS / SIDE PANELS\n// ============================================================================\n\nconst SHEET_SPINNER_HTML = `\n <div class=\"cmm-sheet__loading\">\n <div class=\"cmm-spinner\"></div>\n </div>\n`\n\nconst SHEET_ERROR_HTML = `\n <div class=\"cmm-sheet__error\">\n <p>Failed to load content. Please try the full page.</p>\n </div>\n`\n\nfunction replaceSheetMarkup(element: HTMLElement, markup: string): void {\n element.replaceChildren(createSafeMarkupFragment(markup, { allowSvg: true }))\n}\n\n/**\n * Setup side sheet/drawer components.\n */\nexport function setupSheets(setupConfig: SheetSetupConfig = {}): () => void {\n const {\n openClass = 'cmm-sheet--open',\n hiddenClass = 'cmm-sheet--hidden',\n backdropVisibleClass = 'cmm-sheet__backdrop--visible',\n backdropHiddenClass = 'cmm-sheet__backdrop--hidden',\n spinnerHtml: spinnerMarkup = SHEET_SPINNER_HTML,\n errorHtml: errorMarkup = SHEET_ERROR_HTML,\n allowCrossOriginFetch = false,\n } = setupConfig\n\n const sheetMap = new Map<string, SheetConfig>()\n\n document.querySelectorAll<HTMLElement>('[data-sheet]').forEach(sheet => {\n const key = sheet.dataset.sheet\n if (!key) {\n return\n }\n\n const backdrop = document.querySelector<HTMLElement>(`[data-sheet-backdrop='${key}']`)\n const content = sheet.querySelector<HTMLElement>('[data-sheet-content]')\n if (!backdrop || !content) {\n return\n }\n\n sheetMap.set(key, {\n key,\n sheet,\n backdrop,\n content,\n fullPageLink: sheet.querySelector<HTMLAnchorElement>('[data-sheet-fullpage]'),\n expandLink: sheet.querySelector<HTMLAnchorElement>('[data-sheet-expand]'),\n focusSelector: sheet.dataset.sheetFocus,\n })\n })\n\n if (sheetMap.size === 0) {\n return () => {\n /* noop */\n }\n }\n\n let activeSheet: SheetConfig | null = null\n\n const setOpenState = (config: SheetConfig, open: boolean) => {\n config.sheet.classList.toggle(openClass, open)\n config.sheet.classList.toggle(hiddenClass, !open)\n config.backdrop.classList.toggle(backdropVisibleClass, open)\n config.backdrop.classList.toggle(backdropHiddenClass, !open)\n config.sheet.setAttribute('aria-hidden', open ? 'false' : 'true')\n }\n\n const closeSheet = (config: SheetConfig) => {\n if (activeSheet?.key === config.key) {\n activeSheet = null\n }\n setOpenState(config, false)\n }\n\n const openSheet = async (config: SheetConfig, trigger?: HTMLElement) => {\n if (activeSheet && activeSheet.key !== config.key) {\n closeSheet(activeSheet)\n }\n\n activeSheet = config\n setOpenState(config, true)\n\n const url = trigger?.dataset.sheetUrl ?? config.sheet.dataset.sheetUrl\n const focusSelector = trigger?.dataset.sheetFocus ?? config.focusSelector\n const fullPageHref = trigger?.dataset.sheetFullpage ?? config.sheet.dataset.sheetFullpage\n const expandHref = trigger?.dataset.sheetExpand ?? config.sheet.dataset.sheetExpand\n\n if (config.fullPageLink && fullPageHref) {\n config.fullPageLink.href = fullPageHref\n }\n\n if (config.expandLink && expandHref) {\n config.expandLink.href = expandHref\n }\n\n if (!url || (!allowCrossOriginFetch && !isSameOriginUrl(url))) {\n replaceSheetMarkup(config.content, errorMarkup)\n return\n }\n\n replaceSheetMarkup(config.content, spinnerMarkup)\n\n try {\n const response = await fetch(url)\n if (response.ok) {\n const markup = await response.text()\n replaceSheetMarkup(config.content, markup)\n if (focusSelector) {\n const focusTarget = config.content.querySelector<HTMLElement>(focusSelector)\n focusTarget?.focus()\n }\n } else if (fullPageHref) {\n navigateDocumentTo(fullPageHref)\n } else {\n replaceSheetMarkup(config.content, errorMarkup)\n }\n } catch {\n if (fullPageHref) {\n navigateDocumentTo(fullPageHref)\n } else {\n replaceSheetMarkup(config.content, errorMarkup)\n }\n }\n }\n\n const handlers: { element: HTMLElement; event: string; handler: (e: Event) => void }[] = []\n\n sheetMap.forEach(config => {\n const closeHandler = (event: Event) => {\n const target = event.target as HTMLElement\n if (target.closest('[data-sheet-close]')) {\n event.preventDefault()\n closeSheet(config)\n }\n }\n\n const backdropHandler = () => {\n closeSheet(config)\n }\n\n const expandHandler = () => {\n closeSheet(config)\n }\n\n config.sheet.addEventListener('click', closeHandler)\n config.backdrop.addEventListener('click', backdropHandler)\n handlers.push({ element: config.sheet, event: 'click', handler: closeHandler })\n handlers.push({ element: config.backdrop, event: 'click', handler: backdropHandler })\n\n if (config.expandLink) {\n config.expandLink.addEventListener('click', expandHandler)\n handlers.push({ element: config.expandLink, event: 'click', handler: expandHandler })\n }\n })\n\n // Setup triggers\n document.querySelectorAll<HTMLElement>('[data-sheet-trigger]').forEach(trigger => {\n const key = trigger.dataset.sheetTrigger\n if (!key) {\n return\n }\n\n const config = sheetMap.get(key)\n if (!config) {\n return\n }\n\n const handler = (event: Event) => {\n event.preventDefault()\n void openSheet(config, trigger)\n }\n\n trigger.addEventListener('click', handler)\n handlers.push({ element: trigger, event: 'click', handler })\n })\n\n // Escape key handler\n const escapeHandler = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && activeSheet) {\n closeSheet(activeSheet)\n }\n }\n\n document.addEventListener('keydown', escapeHandler)\n\n // Return cleanup function\n return () => {\n handlers.forEach(({ element, event, handler }) => {\n element.removeEventListener(event, handler)\n })\n document.removeEventListener('keydown', escapeHandler)\n }\n}\n\n// ============================================================================\n// NAVIGATION KEYBOARD SUPPORT\n// ============================================================================\n\n/**\n * Setup keyboard navigation for sidebar nav items.\n */\nexport function setupNavKeyboard(): () => void {\n const navList = document.querySelector<HTMLElement>('[data-nav-list]')\n if (!navList) {\n return () => {\n /* noop */\n }\n }\n\n const getNavItems = () =>\n Array.from(navList.querySelectorAll<HTMLAnchorElement>('[data-nav-item]'))\n\n const handler = (event: KeyboardEvent) => {\n const items = getNavItems()\n const currentIndex = items.indexOf(document.activeElement as HTMLAnchorElement)\n\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0\n items[nextIndex]?.focus()\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1\n items[prevIndex]?.focus()\n } else if (event.key === 'Home') {\n event.preventDefault()\n items[0]?.focus()\n } else if (event.key === 'End') {\n event.preventDefault()\n items[items.length - 1]?.focus()\n }\n }\n\n navList.addEventListener('keydown', handler)\n\n return () => {\n navList.removeEventListener('keydown', handler)\n }\n}\n\n// ============================================================================\n// BOOTSTRAP\n// ============================================================================\n\n/**\n * Initialize all layout components.\n */\nexport function initLayout(\n config: {\n sidebar?: SidebarConfig\n userMenu?: UserMenuConfig\n collapsibles?: CollapsibleConfig\n sheets?: SheetSetupConfig\n } = {}\n): () => void {\n const cleanups = [\n setupSidebar(config.sidebar),\n setupUserMenu(config.userMenu),\n setupCollapsibles(config.collapsibles),\n setupSheets(config.sheets),\n setupNavKeyboard(),\n ]\n\n return () => {\n cleanups.forEach(cleanup => {\n cleanup()\n })\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/layout/index.ts"],"sourcesContent":["/**\n * Layout Components\n * @countermeasure/web-components\n *\n * Shared layout utilities for sidebar, user menu, collapsibles, and sheets.\n */\n\nimport { createSafeMarkupFragment } from '../utils/sanitize'\nimport { navigateDocumentTo } from '../utils/navigation'\nexport {\n AppShell,\n TopMenuBar,\n createAppShell,\n createProductShell,\n createTopMenuBar,\n} from './app-shell'\nexport type {\n AppShellConfig,\n TopMenuBarAction,\n TopMenuBarActionInput,\n TopMenuBarBreadcrumb,\n TopMenuBarConfig,\n TopMenuBarUser,\n} from './app-shell'\nexport {\n CORE_APP_ROUTE_LABELS,\n createCoreAppChromePreset,\n createCoreAppSidebarConfig,\n createCoreAppSidebarSections,\n createCoreAppTopbarActions,\n createCoreAppTopbarConfig,\n getCoreAppRouteLabel,\n mountCoreAppChrome,\n mountCoreAppChromeFromDom,\n} from './core-app-chrome'\nexport type {\n CoreAppChromePreset,\n CoreAppChromeDomMountOptions,\n CoreAppChromeMount,\n CoreAppChromeMountOptions,\n CoreAppChromePresetOptions,\n CoreAppRouteLabel,\n CoreAppSidebarPresetOptions,\n CoreAppTopbarPresetOptions,\n} from './core-app-chrome'\nexport { PageHeader, createPageHeader } from './page-header'\nexport type { PageHeaderConfig, PageHeaderMetadata } from './page-header'\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface SidebarConfig {\n storageKey?: string\n desktopBreakpoint?: string\n collapsedClass?: string\n openClass?: string\n}\n\nexport interface UserMenuConfig {\n triggerSelector?: string\n dropdownSelector?: string\n dropdownItemSelector?: string\n}\n\nexport interface CollapsibleConfig {\n scrollIntoView?: boolean\n scrollBehavior?: ScrollBehavior\n hiddenClass?: string\n}\n\nexport interface SheetConfig {\n key: string\n sheet: HTMLElement\n backdrop: HTMLElement\n content: HTMLElement\n fullPageLink?: HTMLAnchorElement | null | undefined\n expandLink?: HTMLAnchorElement | null | undefined\n focusSelector?: string | undefined\n}\n\nexport interface SheetSetupConfig {\n openClass?: string\n hiddenClass?: string\n backdropVisibleClass?: string\n backdropHiddenClass?: string\n spinnerHtml?: string\n errorHtml?: string\n /** Allow fetching sheet content from non-same-origin URLs. Default: false. */\n allowCrossOriginFetch?: boolean\n}\n\n// ============================================================================\n// STORAGE HELPERS\n// ============================================================================\n\nconst resolveStorage = (): Storage | null => {\n if (typeof window === 'undefined') {\n return null\n }\n try {\n return window.localStorage\n } catch {\n console.warn('Unable to access localStorage')\n return null\n }\n}\n\nconst isSameOriginUrl = (url: string): boolean => {\n try {\n return new URL(url, window.location.href).origin === window.location.origin\n } catch {\n return false\n }\n}\n\n// ============================================================================\n// SIDEBAR\n// ============================================================================\n\nconst DEFAULT_SIDEBAR_CONFIG: Required<SidebarConfig> = {\n storageKey: 'cmm-sidebar-state',\n desktopBreakpoint: '(min-width: 1024px)',\n collapsedClass: 'cmm-shell--sidebar-collapsed',\n openClass: 'cmm-shell--sidebar-open',\n}\n\n/**\n * Setup responsive sidebar with collapse/expand functionality.\n */\nexport function setupSidebar(config: SidebarConfig = {}): () => void {\n const options = { ...DEFAULT_SIDEBAR_CONFIG, ...config }\n const layout = document.querySelector<HTMLElement>('[data-shell-layout]')\n const sidebar = document.querySelector<HTMLElement>('[data-shell-sidebar]')\n\n if (!layout || !sidebar) {\n return () => {\n /* noop */\n }\n }\n\n const storage = resolveStorage()\n const mediaQuery = window.matchMedia(options.desktopBreakpoint)\n const toggles = Array.from(document.querySelectorAll<HTMLElement>('[data-sidebar-toggle]'))\n const dismissors = Array.from(document.querySelectorAll<HTMLElement>('[data-sidebar-dismiss]'))\n\n const sidebarId = sidebar.id || 'shell-sidebar'\n sidebar.id = sidebarId\n\n const readStoredCollapsed = () => storage?.getItem(options.storageKey) === 'collapsed'\n\n const syncToggleAria = () => {\n const expanded = mediaQuery.matches\n ? !layout.classList.contains(options.collapsedClass)\n : layout.classList.contains(options.openClass)\n\n toggles.forEach(toggle => {\n toggle.setAttribute('aria-expanded', expanded.toString())\n toggle.setAttribute('aria-controls', sidebarId)\n })\n }\n\n const applyCollapsed = (collapsed: boolean, persist = true) => {\n layout.classList.toggle(options.collapsedClass, collapsed)\n if (persist && mediaQuery.matches) {\n storage?.setItem(options.storageKey, collapsed ? 'collapsed' : 'expanded')\n }\n syncToggleAria()\n }\n\n const setOpen = (open: boolean) => {\n layout.classList.toggle(options.openClass, open)\n syncToggleAria()\n }\n\n const syncForViewport = () => {\n if (mediaQuery.matches) {\n setOpen(false)\n applyCollapsed(readStoredCollapsed(), false)\n } else {\n setOpen(false)\n applyCollapsed(false, false)\n }\n }\n\n const handleToggleClick = () => {\n if (mediaQuery.matches) {\n const collapsed = layout.classList.contains(options.collapsedClass)\n applyCollapsed(!collapsed)\n } else {\n const open = layout.classList.contains(options.openClass)\n setOpen(!open)\n }\n }\n\n const handleDismissClick = () => {\n setOpen(false)\n }\n\n const handleKeydown = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && !mediaQuery.matches) {\n setOpen(false)\n }\n }\n\n // Attach listeners\n toggles.forEach(toggle => {\n toggle.addEventListener('click', handleToggleClick)\n })\n dismissors.forEach(dismiss => {\n dismiss.addEventListener('click', handleDismissClick)\n })\n document.addEventListener('keydown', handleKeydown)\n mediaQuery.addEventListener('change', syncForViewport)\n\n // Initialize\n syncForViewport()\n\n // Return cleanup function\n return () => {\n toggles.forEach(toggle => {\n toggle.removeEventListener('click', handleToggleClick)\n })\n dismissors.forEach(dismiss => {\n dismiss.removeEventListener('click', handleDismissClick)\n })\n document.removeEventListener('keydown', handleKeydown)\n mediaQuery.removeEventListener('change', syncForViewport)\n }\n}\n\n// ============================================================================\n// USER MENU\n// ============================================================================\n\n/**\n * Setup user menu dropdown with keyboard navigation.\n */\nexport function setupUserMenu(config: UserMenuConfig = {}): () => void {\n const triggerSelector = config.triggerSelector ?? '[data-user-trigger]'\n const dropdownSelector = config.dropdownSelector ?? '[data-user-dropdown]'\n\n const userMenu = document.querySelector<HTMLElement>('[data-user-menu]')\n const trigger = document.querySelector<HTMLButtonElement>(triggerSelector)\n const dropdown = document.querySelector<HTMLElement>(dropdownSelector)\n\n if (!userMenu || !trigger || !dropdown) {\n return () => {\n /* noop */\n }\n }\n\n const setExpanded = (expanded: boolean) => {\n trigger.setAttribute('aria-expanded', expanded.toString())\n userMenu.setAttribute('aria-expanded', expanded.toString())\n }\n\n const isExpanded = () => trigger.getAttribute('aria-expanded') === 'true'\n\n const handleTriggerClick = (event: Event) => {\n event.stopPropagation()\n setExpanded(!isExpanded())\n }\n\n const handleDocumentClick = (event: Event) => {\n if (!userMenu.contains(event.target as Node)) {\n setExpanded(false)\n }\n }\n\n const handleKeydown = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && isExpanded()) {\n setExpanded(false)\n trigger.focus()\n }\n }\n\n const dropdownItemSelector = config.dropdownItemSelector ?? '.cmm-user-menu__dropdown-item'\n\n const handleDropdownKeydown = (event: KeyboardEvent) => {\n const items = Array.from(dropdown.querySelectorAll<HTMLAnchorElement>(dropdownItemSelector))\n const currentIndex = items.indexOf(document.activeElement as HTMLAnchorElement)\n\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0\n items[nextIndex]?.focus()\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1\n items[prevIndex]?.focus()\n } else if (event.key === 'Tab' && !event.shiftKey && currentIndex === items.length - 1) {\n setExpanded(false)\n } else if (event.key === 'Tab' && event.shiftKey && currentIndex === 0) {\n setExpanded(false)\n }\n }\n\n const handleTriggerKeydown = (event: KeyboardEvent) => {\n if (\n (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') &&\n !isExpanded()\n ) {\n event.preventDefault()\n setExpanded(true)\n setTimeout(() => {\n const firstItem = dropdown.querySelector<HTMLAnchorElement>(dropdownItemSelector)\n firstItem?.focus()\n }, 50)\n }\n }\n\n // Attach listeners\n trigger.addEventListener('click', handleTriggerClick)\n document.addEventListener('click', handleDocumentClick)\n document.addEventListener('keydown', handleKeydown)\n dropdown.addEventListener('keydown', handleDropdownKeydown)\n trigger.addEventListener('keydown', handleTriggerKeydown)\n\n // Return cleanup function\n return () => {\n trigger.removeEventListener('click', handleTriggerClick)\n document.removeEventListener('click', handleDocumentClick)\n document.removeEventListener('keydown', handleKeydown)\n dropdown.removeEventListener('keydown', handleDropdownKeydown)\n trigger.removeEventListener('keydown', handleTriggerKeydown)\n }\n}\n\n// ============================================================================\n// COLLAPSIBLES\n// ============================================================================\n\nconst isCollapsibleExpanded = (section: HTMLElement) =>\n section.dataset.collapsibleOpen === 'true' || !section.classList.contains('hidden')\n\nlet collapsibleHiddenClass = 'cmm-collapsible--hidden'\n\nconst updateCollapsibleState = (section: HTMLElement, expanded: boolean) => {\n section.classList.toggle('hidden', !expanded)\n section.classList.toggle(collapsibleHiddenClass, !expanded)\n section.dataset.collapsibleOpen = expanded.toString()\n section.setAttribute('aria-hidden', expanded ? 'false' : 'true')\n}\n\nconst syncCollapsibleToggles = (selector: string, expanded: boolean) => {\n document.querySelectorAll<HTMLElement>(`[data-toggle-target='${selector}']`).forEach(toggle => {\n toggle.setAttribute('aria-expanded', expanded.toString())\n })\n}\n\n/**\n * Setup collapsible sections with toggle buttons.\n */\nexport function setupCollapsibles(config: CollapsibleConfig = {}): () => void {\n const {\n scrollIntoView = true,\n scrollBehavior = 'smooth',\n hiddenClass = 'cmm-collapsible--hidden',\n } = config\n collapsibleHiddenClass = hiddenClass\n\n // Initialize all collapsible sections\n document.querySelectorAll<HTMLElement>('[data-collapsible]').forEach(section => {\n const expanded =\n section.dataset.collapsibleOpen === 'true' || !section.classList.contains('hidden')\n updateCollapsibleState(section, expanded)\n })\n\n const handlers: { element: HTMLElement; handler: (e: Event) => void }[] = []\n\n // Setup toggle triggers\n document.querySelectorAll<HTMLElement>('[data-toggle-target]').forEach(toggle => {\n const selector = toggle.dataset.toggleTarget\n if (!selector) {\n return\n }\n\n const target = document.querySelector<HTMLElement>(selector)\n if (!target) {\n return\n }\n\n const ariaTarget = target.id || selector.replace(/^#/, '')\n toggle.setAttribute('aria-controls', ariaTarget)\n toggle.setAttribute('aria-expanded', isCollapsibleExpanded(target).toString())\n\n const handler = (event: Event) => {\n event.preventDefault()\n const expanded = !isCollapsibleExpanded(target)\n updateCollapsibleState(target, expanded)\n syncCollapsibleToggles(selector, expanded)\n\n if (expanded && scrollIntoView) {\n target.scrollIntoView({ behavior: scrollBehavior, block: 'start' })\n }\n }\n\n toggle.addEventListener('click', handler)\n handlers.push({ element: toggle, handler })\n })\n\n // Setup collapse triggers (close only)\n document.querySelectorAll<HTMLElement>('[data-collapse-target]').forEach(toggle => {\n const selector = toggle.dataset.collapseTarget\n if (!selector) {\n return\n }\n\n const target = document.querySelector<HTMLElement>(selector)\n if (!target) {\n return\n }\n\n const handler = (event: Event) => {\n event.preventDefault()\n updateCollapsibleState(target, false)\n syncCollapsibleToggles(selector, false)\n }\n\n toggle.addEventListener('click', handler)\n handlers.push({ element: toggle, handler })\n })\n\n // Handle URL hash for deep linking\n const hash = window.location.hash\n if (hash) {\n const target = document.querySelector<HTMLElement>(hash)\n if (target?.dataset.collapsible !== undefined) {\n updateCollapsibleState(target, true)\n syncCollapsibleToggles(hash, true)\n }\n }\n\n // Return cleanup function\n return () => {\n handlers.forEach(({ element, handler }) => {\n element.removeEventListener('click', handler)\n })\n }\n}\n\n// ============================================================================\n// SHEETS / SIDE PANELS\n// ============================================================================\n\nconst SHEET_SPINNER_HTML = `\n <div class=\"cmm-sheet__loading\">\n <div class=\"cmm-spinner\"></div>\n </div>\n`\n\nconst SHEET_ERROR_HTML = `\n <div class=\"cmm-sheet__error\">\n <p>Failed to load content. Please try the full page.</p>\n </div>\n`\n\nfunction replaceSheetMarkup(element: HTMLElement, markup: string): void {\n element.replaceChildren(createSafeMarkupFragment(markup, { allowSvg: true }))\n}\n\n/**\n * Setup side sheet/drawer components.\n */\nexport function setupSheets(setupConfig: SheetSetupConfig = {}): () => void {\n const {\n openClass = 'cmm-sheet--open',\n hiddenClass = 'cmm-sheet--hidden',\n backdropVisibleClass = 'cmm-sheet__backdrop--visible',\n backdropHiddenClass = 'cmm-sheet__backdrop--hidden',\n spinnerHtml: spinnerMarkup = SHEET_SPINNER_HTML,\n errorHtml: errorMarkup = SHEET_ERROR_HTML,\n allowCrossOriginFetch = false,\n } = setupConfig\n\n const sheetMap = new Map<string, SheetConfig>()\n\n document.querySelectorAll<HTMLElement>('[data-sheet]').forEach(sheet => {\n const key = sheet.dataset.sheet\n if (!key) {\n return\n }\n\n const backdrop = document.querySelector<HTMLElement>(`[data-sheet-backdrop='${key}']`)\n const content = sheet.querySelector<HTMLElement>('[data-sheet-content]')\n if (!backdrop || !content) {\n return\n }\n\n sheetMap.set(key, {\n key,\n sheet,\n backdrop,\n content,\n fullPageLink: sheet.querySelector<HTMLAnchorElement>('[data-sheet-fullpage]'),\n expandLink: sheet.querySelector<HTMLAnchorElement>('[data-sheet-expand]'),\n focusSelector: sheet.dataset.sheetFocus,\n })\n })\n\n if (sheetMap.size === 0) {\n return () => {\n /* noop */\n }\n }\n\n let activeSheet: SheetConfig | null = null\n\n const setOpenState = (config: SheetConfig, open: boolean) => {\n config.sheet.classList.toggle(openClass, open)\n config.sheet.classList.toggle(hiddenClass, !open)\n config.backdrop.classList.toggle(backdropVisibleClass, open)\n config.backdrop.classList.toggle(backdropHiddenClass, !open)\n config.sheet.setAttribute('aria-hidden', open ? 'false' : 'true')\n }\n\n const closeSheet = (config: SheetConfig) => {\n if (activeSheet?.key === config.key) {\n activeSheet = null\n }\n setOpenState(config, false)\n }\n\n const openSheet = async (config: SheetConfig, trigger?: HTMLElement) => {\n if (activeSheet && activeSheet.key !== config.key) {\n closeSheet(activeSheet)\n }\n\n activeSheet = config\n setOpenState(config, true)\n\n const url = trigger?.dataset.sheetUrl ?? config.sheet.dataset.sheetUrl\n const focusSelector = trigger?.dataset.sheetFocus ?? config.focusSelector\n const fullPageHref = trigger?.dataset.sheetFullpage ?? config.sheet.dataset.sheetFullpage\n const expandHref = trigger?.dataset.sheetExpand ?? config.sheet.dataset.sheetExpand\n\n if (config.fullPageLink && fullPageHref) {\n config.fullPageLink.href = fullPageHref\n }\n\n if (config.expandLink && expandHref) {\n config.expandLink.href = expandHref\n }\n\n if (!url || (!allowCrossOriginFetch && !isSameOriginUrl(url))) {\n replaceSheetMarkup(config.content, errorMarkup)\n return\n }\n\n replaceSheetMarkup(config.content, spinnerMarkup)\n\n try {\n const response = await fetch(url)\n if (response.ok) {\n const markup = await response.text()\n replaceSheetMarkup(config.content, markup)\n if (focusSelector) {\n const focusTarget = config.content.querySelector<HTMLElement>(focusSelector)\n focusTarget?.focus()\n }\n } else if (fullPageHref) {\n navigateDocumentTo(fullPageHref)\n } else {\n replaceSheetMarkup(config.content, errorMarkup)\n }\n } catch {\n if (fullPageHref) {\n navigateDocumentTo(fullPageHref)\n } else {\n replaceSheetMarkup(config.content, errorMarkup)\n }\n }\n }\n\n const handlers: { element: HTMLElement; event: string; handler: (e: Event) => void }[] = []\n\n sheetMap.forEach(config => {\n const closeHandler = (event: Event) => {\n const target = event.target as HTMLElement\n if (target.closest('[data-sheet-close]')) {\n event.preventDefault()\n closeSheet(config)\n }\n }\n\n const backdropHandler = () => {\n closeSheet(config)\n }\n\n const expandHandler = () => {\n closeSheet(config)\n }\n\n config.sheet.addEventListener('click', closeHandler)\n config.backdrop.addEventListener('click', backdropHandler)\n handlers.push({ element: config.sheet, event: 'click', handler: closeHandler })\n handlers.push({ element: config.backdrop, event: 'click', handler: backdropHandler })\n\n if (config.expandLink) {\n config.expandLink.addEventListener('click', expandHandler)\n handlers.push({ element: config.expandLink, event: 'click', handler: expandHandler })\n }\n })\n\n // Setup triggers\n document.querySelectorAll<HTMLElement>('[data-sheet-trigger]').forEach(trigger => {\n const key = trigger.dataset.sheetTrigger\n if (!key) {\n return\n }\n\n const config = sheetMap.get(key)\n if (!config) {\n return\n }\n\n const handler = (event: Event) => {\n event.preventDefault()\n void openSheet(config, trigger)\n }\n\n trigger.addEventListener('click', handler)\n handlers.push({ element: trigger, event: 'click', handler })\n })\n\n // Escape key handler\n const escapeHandler = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && activeSheet) {\n closeSheet(activeSheet)\n }\n }\n\n document.addEventListener('keydown', escapeHandler)\n\n // Return cleanup function\n return () => {\n handlers.forEach(({ element, event, handler }) => {\n element.removeEventListener(event, handler)\n })\n document.removeEventListener('keydown', escapeHandler)\n }\n}\n\n// ============================================================================\n// NAVIGATION KEYBOARD SUPPORT\n// ============================================================================\n\n/**\n * Setup keyboard navigation for sidebar nav items.\n */\nexport function setupNavKeyboard(): () => void {\n const navList = document.querySelector<HTMLElement>('[data-nav-list]')\n if (!navList) {\n return () => {\n /* noop */\n }\n }\n\n const getNavItems = () =>\n Array.from(navList.querySelectorAll<HTMLAnchorElement>('[data-nav-item]'))\n\n const handler = (event: KeyboardEvent) => {\n const items = getNavItems()\n const currentIndex = items.indexOf(document.activeElement as HTMLAnchorElement)\n\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0\n items[nextIndex]?.focus()\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1\n items[prevIndex]?.focus()\n } else if (event.key === 'Home') {\n event.preventDefault()\n items[0]?.focus()\n } else if (event.key === 'End') {\n event.preventDefault()\n items[items.length - 1]?.focus()\n }\n }\n\n navList.addEventListener('keydown', handler)\n\n return () => {\n navList.removeEventListener('keydown', handler)\n }\n}\n\n// ============================================================================\n// BOOTSTRAP\n// ============================================================================\n\n/**\n * Initialize all layout components.\n */\nexport function initLayout(\n config: {\n sidebar?: SidebarConfig\n userMenu?: UserMenuConfig\n collapsibles?: CollapsibleConfig\n sheets?: SheetSetupConfig\n } = {}\n): () => void {\n const cleanups = [\n setupSidebar(config.sidebar),\n setupUserMenu(config.userMenu),\n setupCollapsibles(config.collapsibles),\n setupSheets(config.sheets),\n setupNavKeyboard(),\n ]\n\n return () => {\n cleanups.forEach(cleanup => {\n cleanup()\n })\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { VariantProps } from 'class-variance-authority';
2
2
  import * as React from 'react';
3
3
  declare const badgeVariants: (props?: ({
4
- variant?: "glass" | "orange" | "cyan" | "violet" | "rose" | "emerald" | "amber" | "outline" | "default" | "destructive" | "secondary" | "blue" | "live" | null | undefined;
4
+ variant?: "glass" | "orange" | "cyan" | "violet" | "rose" | "emerald" | "default" | "amber" | "outline" | "destructive" | "secondary" | "blue" | "live" | null | undefined;
5
5
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
6
6
  export type BadgeSeverity = 'critical' | 'high' | 'medium' | 'low' | 'info';
7
7
  export type BadgeStatus = 'active' | 'inactive' | 'pending' | 'error';
@@ -9,8 +9,8 @@ import * as React from 'react';
9
9
  * `glass-primary` — see CVA mapping below.
10
10
  */
11
11
  declare const buttonVariants: (props?: ({
12
- variant?: "glass" | "link" | "outline" | "default" | "ghost" | "destructive" | "primary" | "secondary" | "glass-primary" | null | undefined;
13
- size?: "default" | "sm" | "lg" | "icon" | "icon-sm" | "icon-lg" | null | undefined;
12
+ variant?: "glass" | "link" | "default" | "primary" | "outline" | "ghost" | "destructive" | "secondary" | "glass-primary" | null | undefined;
13
+ size?: "default" | "icon" | "sm" | "lg" | "icon-sm" | "icon-lg" | null | undefined;
14
14
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
15
15
  export type ButtonTone = 'glass' | 'shadcn';
16
16
  export type ButtonProps = React.ComponentProps<'button'> & VariantProps<typeof buttonVariants> & {
@@ -2,7 +2,7 @@ import { VariantProps } from 'class-variance-authority';
2
2
  import * as React from 'react';
3
3
  declare const copyButtonVariants: (props?: ({
4
4
  variant?: "default" | "ghost" | null | undefined;
5
- size?: "default" | "sm" | "icon" | "icon-sm" | null | undefined;
5
+ size?: "default" | "icon" | "sm" | "icon-sm" | null | undefined;
6
6
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
7
7
  export interface CopyButtonProps extends Omit<React.ComponentProps<'button'>, 'children'>, VariantProps<typeof copyButtonVariants> {
8
8
  /** The text to copy to clipboard */
@@ -6,7 +6,7 @@ export interface TrendConfig {
6
6
  label?: string;
7
7
  }
8
8
  declare const trendVariants: (props?: ({
9
- direction?: "up" | "down" | "neutral" | null | undefined;
9
+ direction?: "neutral" | "up" | "down" | null | undefined;
10
10
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
11
11
  export interface StatCardProps extends React.ComponentProps<'div'> {
12
12
  label: string;
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import * as TogglePrimitive from '@radix-ui/react-toggle';
4
4
  import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
5
5
  declare const toggleVariants: (props?: ({
6
- variant?: "outline" | "default" | null | undefined;
6
+ variant?: "default" | "outline" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | null | undefined;
8
8
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
9
9
  declare function Toggle({ className, variant, size, ...props }: React.ComponentProps<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>): import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { SidebarComponentConfig, SidebarNavItem, SidebarSection, SidebarUser, SidebarFooterAction } from '../sidebar/types';
1
+ import { SidebarComponentConfig, SidebarBadgeConfig, SidebarBadgeTone, SidebarNavItem, SidebarScopeConfig, SidebarScopeOption, SidebarSection, SidebarUser, SidebarFooterAction } from '../sidebar/types';
2
2
  /**
3
3
  * Sidebar React wrapper — Lane A imperative wrap over src/sidebar/component.ts
4
4
  *
@@ -6,8 +6,8 @@ import { SidebarComponentConfig, SidebarNavItem, SidebarSection, SidebarUser, Si
6
6
  * import { Sidebar } from '@countermeasure-platform/web-components/react/sidebar'
7
7
  *
8
8
  * The wrapper renders a host div then mounts `SidebarComponent` into it on
9
- * mount. Prop changes to `activeItemId` and controlled `open` are synced via
10
- * the class's public API.
9
+ * mount. Prop changes to `activeItemId`, `pathname`, and controlled `open`
10
+ * are synced via the class's public API.
11
11
  */
12
12
  import * as React from 'react';
13
13
  export interface SidebarProps extends Omit<SidebarComponentConfig, 'container' | 'onNavigate' | 'onCollapse'> {
@@ -19,7 +19,7 @@ export interface SidebarProps extends Omit<SidebarComponentConfig, 'container' |
19
19
  onCollapse?: (collapsed: boolean) => void;
20
20
  className?: string;
21
21
  }
22
- export type { SidebarNavItem, SidebarSection, SidebarUser, SidebarFooterAction };
22
+ export type { SidebarBadgeConfig, SidebarBadgeTone, SidebarFooterAction, SidebarNavItem, SidebarScopeConfig, SidebarScopeOption, SidebarSection, SidebarUser, };
23
23
  export interface SidebarCollapseButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
24
24
  collapsed?: boolean;
25
25
  edge?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar.d.ts","sourceRoot":"","sources":["../../src/react/sidebar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EACV,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,WAAW,EACX,mBAAmB,EACpB,MAAM,kBAAkB,CAAA;AAEzB,MAAM,WAAW,YAAa,SAAQ,IAAI,CACxC,sBAAsB,EACtB,WAAW,GAAG,YAAY,GAAG,YAAY,CAC1C;IACC,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAA;IAC3C,qDAAqD;IACrD,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAA;IACzC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAA;AAEhF,MAAM,WAAW,0BAA2B,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IAC/F,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,QAAA,MAAM,OAAO;YAAW,YAAY;;CA6CnC,CAAA;AAwBD,QAAA,MAAM,qBAAqB;qBAAoB,0BAA0B;;CA2BxE,CAAA;AAID,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAA"}
1
+ {"version":3,"file":"sidebar.d.ts","sourceRoot":"","sources":["../../src/react/sidebar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,mBAAmB,EACpB,MAAM,kBAAkB,CAAA;AAEzB,MAAM,WAAW,YAAa,SAAQ,IAAI,CACxC,sBAAsB,EACtB,WAAW,GAAG,YAAY,GAAG,YAAY,CAC1C;IACC,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAA;IAC3C,qDAAqD;IACrD,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAA;IACzC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,WAAW,GACZ,CAAA;AAED,MAAM,WAAW,0BAA2B,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IAC/F,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,QAAA,MAAM,OAAO;YAAW,YAAY;;CAsDnC,CAAA;AAwBD,QAAA,MAAM,qBAAqB;qBAAoB,0BAA0B;;CA2BxE,CAAA;AAID,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAA"}
@@ -1,33 +1,35 @@
1
- import { t as e } from "../component-Bxhxf21c.js";
1
+ import { t as e } from "../component-D5sRm1fq.js";
2
2
  import { t } from "../utils-BC9GT6Pr.js";
3
3
  import * as n from "react";
4
4
  import { jsx as r } from "react/jsx-runtime";
5
5
  //#region src/react/sidebar.tsx
6
6
  var i = (i) => {
7
- let { activeItemId: a, onNavigate: o, onCollapse: s, className: c, open: l, ...u } = i, d = n.useRef(null), f = n.useRef(null), p = n.useRef(o), m = n.useRef(s);
8
- return p.current = o, m.current = s, n.useEffect(() => {
9
- if (typeof document > "u" || !d.current) return;
7
+ let { activeItemId: a, pathname: o, onNavigate: s, onCollapse: c, className: l, open: u, ...d } = i, f = n.useRef(null), p = n.useRef(null), m = n.useRef(s), h = n.useRef(c);
8
+ return m.current = s, h.current = c, n.useEffect(() => {
9
+ if (typeof document > "u" || !f.current) return;
10
10
  let t = {
11
- ...u,
12
- container: d.current,
11
+ ...d,
12
+ container: f.current,
13
13
  onNavigate: (e) => {
14
- p.current?.(e);
14
+ m.current?.(e);
15
15
  },
16
16
  onCollapse: (e) => {
17
- m.current?.(e);
17
+ h.current?.(e);
18
18
  }
19
19
  };
20
- return l !== void 0 && (t.open = l), f.current = new e(t), () => {
21
- f.current?.destroy(), f.current = null;
20
+ return u !== void 0 && (t.open = u), o !== void 0 && (t.pathname = o), p.current = new e(t), () => {
21
+ p.current?.destroy(), p.current = null;
22
22
  };
23
23
  }, []), n.useEffect(() => {
24
- f.current && f.current.setOpen(l === !0);
25
- }, [l]), n.useEffect(() => {
26
- !f.current || a === void 0 || f.current.setActive(a);
27
- }, [a]), /* @__PURE__ */ r("div", {
28
- ref: d,
24
+ p.current && p.current.setOpen(u === !0);
25
+ }, [u]), n.useEffect(() => {
26
+ !p.current || a === void 0 || p.current.setActive(a);
27
+ }, [a]), n.useEffect(() => {
28
+ !p.current || a !== void 0 || p.current.setPathname(o);
29
+ }, [a, o]), /* @__PURE__ */ r("div", {
30
+ ref: f,
29
31
  "data-slot": "sidebar",
30
- className: t(c)
32
+ className: t(l)
31
33
  });
32
34
  };
33
35
  i.displayName = "Sidebar";