ardo 3.5.0 → 3.6.1

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 (70) hide show
  1. package/README.md +1 -1
  2. package/dist/{DocPage-EIVMae_6.js → DocPage-Yt8MMpBg.js} +351 -67
  3. package/dist/DocPage-Yt8MMpBg.js.map +1 -0
  4. package/dist/assets/src/ui/components/Accordion.css.ts.vanilla-BPEoNgJt.css +103 -0
  5. package/dist/assets/src/ui/components/Badge.css.ts.vanilla-D9jsj0rg.css +44 -0
  6. package/dist/assets/src/ui/components/Card.css.ts.vanilla-CO6uiEzC.css +97 -0
  7. package/dist/assets/src/ui/theme/{dark.css.ts.vanilla-CSJkJvIz.css → dark.css.ts.vanilla-yFCWJiX4.css} +3 -0
  8. package/dist/assets/src/ui/theme/{light.css.ts.vanilla-CFz9jeJK.css → light.css.ts.vanilla-DDKnXFOi.css} +3 -0
  9. package/dist/{brand-icons-Di8w0Nu9.js → brand-icons-DBTSSnty.js} +1 -1
  10. package/dist/{brand-icons-Di8w0Nu9.js.map → brand-icons-DBTSSnty.js.map} +1 -1
  11. package/dist/config/index.d.ts +2 -2
  12. package/dist/config/index.d.ts.map +1 -1
  13. package/dist/config/index.js +5 -0
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/{contract.css-eFQbUr4z.d.ts → contract.css-BwTjjNXQ.d.ts} +4 -1
  16. package/dist/contract.css-BwTjjNXQ.d.ts.map +1 -0
  17. package/dist/{favicon-Cx-inut3.js → favicon-DN3ymUVm.js} +1 -1
  18. package/dist/{favicon-Cx-inut3.js.map → favicon-DN3ymUVm.js.map} +1 -1
  19. package/dist/{generator-CYSyo4Vz.js → generator-Cc4WGRdf.js} +1 -1
  20. package/dist/{generator-CYSyo4Vz.js.map → generator-Cc4WGRdf.js.map} +1 -1
  21. package/dist/icons/index.d.ts +5 -6
  22. package/dist/icons/index.d.ts.map +1 -1
  23. package/dist/icons/index.js +1 -1
  24. package/dist/{index-CuMTHUxX.d.ts → index-BSWG2TdH.d.ts} +6 -8
  25. package/dist/index-BSWG2TdH.d.ts.map +1 -0
  26. package/dist/{index-BcekgOfA.d.ts → index-Dzu13I_t.d.ts} +138 -47
  27. package/dist/index-Dzu13I_t.d.ts.map +1 -0
  28. package/dist/index.d.ts +3 -3
  29. package/dist/index.js +3 -3
  30. package/dist/mdx/provider.d.ts +1 -1
  31. package/dist/mdx/provider.d.ts.map +1 -1
  32. package/dist/mdx/provider.js +6 -1
  33. package/dist/mdx/provider.js.map +1 -1
  34. package/dist/runtime/index.d.ts +2 -2
  35. package/dist/runtime/index.js +1 -1
  36. package/dist/{sidebar-utils-C06DJsx4.js → sidebar-utils-tTBVAPLE.js} +1 -1
  37. package/dist/{sidebar-utils-C06DJsx4.js.map → sidebar-utils-tTBVAPLE.js.map} +1 -1
  38. package/dist/theme/index.d.ts +7 -1
  39. package/dist/theme/index.d.ts.map +1 -1
  40. package/dist/theme/index.js +9 -0
  41. package/dist/theme/index.js.map +1 -1
  42. package/dist/typedoc/components/index.d.ts +8 -8
  43. package/dist/typedoc/components/index.d.ts.map +1 -1
  44. package/dist/typedoc/components/index.js.map +1 -1
  45. package/dist/typedoc/index.d.ts +1 -1
  46. package/dist/typedoc/index.d.ts.map +1 -1
  47. package/dist/typedoc/index.js +3 -3
  48. package/dist/typedoc/index.js.map +1 -1
  49. package/dist/{types-Ck2Vm7NB.d.ts → types-DZKj8kWR.d.ts} +1 -1
  50. package/dist/types-DZKj8kWR.d.ts.map +1 -0
  51. package/dist/{types-B75OhnGa.d.ts → types-iGO1oGpR.d.ts} +49 -4
  52. package/dist/types-iGO1oGpR.d.ts.map +1 -0
  53. package/dist/ui/index.d.ts +2 -2
  54. package/dist/ui/index.js +3 -3
  55. package/dist/ui/styles.css +206 -0
  56. package/dist/ui/styles.js +4 -2
  57. package/dist/{ui-B6X8gAvz.js → ui-mBEFGR-s.js} +316 -138
  58. package/dist/ui-mBEFGR-s.js.map +1 -0
  59. package/dist/vite/index.d.ts +1 -1
  60. package/dist/vite/index.d.ts.map +1 -1
  61. package/dist/vite/index.js +543 -33
  62. package/dist/vite/index.js.map +1 -1
  63. package/package.json +15 -15
  64. package/dist/DocPage-EIVMae_6.js.map +0 -1
  65. package/dist/contract.css-eFQbUr4z.d.ts.map +0 -1
  66. package/dist/index-BcekgOfA.d.ts.map +0 -1
  67. package/dist/index-CuMTHUxX.d.ts.map +0 -1
  68. package/dist/types-B75OhnGa.d.ts.map +0 -1
  69. package/dist/types-Ck2Vm7NB.d.ts.map +0 -1
  70. package/dist/ui-B6X8gAvz.js.map +0 -1
@@ -1,7 +1,7 @@
1
- import { a as ArdoProvider, d as useArdoSidebar, l as useArdoContexts, o as ArdoSiteConfigProvider, s as useArdoConfig, u as useArdoPageData } from "./sidebar-utils-C06DJsx4.js";
2
- import { $ as footerLink, A as home, B as MonitorIcon, F as ChevronDownIcon, G as SunIcon, H as PackageIcon, I as CodeIcon, K as WrenchIcon, L as FileCodeIcon, M as layout, N as BookOpenIcon, O as ArdoLayout, P as BoxIcon, R as FileTextIcon, U as SearchIcon, V as MoonIcon, W as SettingsIcon, Y as footerArdoLink, Z as footerContainer, j as homeMain, nt as footerPrimary, q as XIcon, tt as footerOwl, z as MessageCircleIcon } from "./DocPage-EIVMae_6.js";
3
- import { c as TwitterIcon, i as LinkedinIcon, n as GithubIcon, o as NpmIcon, u as YoutubeIcon } from "./brand-icons-Di8w0Nu9.js";
4
- import React, { Children, cloneElement, createContext, isValidElement, use, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
1
+ import { a as ArdoProvider, d as useArdoSidebar, l as useArdoContexts, o as ArdoSiteConfigProvider, s as useArdoConfig, u as useArdoPageData } from "./sidebar-utils-tTBVAPLE.js";
2
+ import { B as ChevronDownIcon, F as home, G as MonitorIcon, H as FileCodeIcon, I as homeMain, J as SearchIcon, K as MoonIcon, L as layout, N as ArdoLayout, Q as XIcon, R as BookOpenIcon, U as FileTextIcon, V as CodeIcon, W as MessageCircleIcon, X as SunIcon, Y as SettingsIcon, Z as WrenchIcon, at as footerMessage, et as footerArdoLink, it as footerLink, nt as footerContainer, ot as footerOwl, q as PackageIcon, rt as footerCopyright, st as footerPrimary, z as BoxIcon } from "./DocPage-Yt8MMpBg.js";
3
+ import { c as TwitterIcon, i as LinkedinIcon, n as GithubIcon, o as NpmIcon, u as YoutubeIcon } from "./brand-icons-DBTSSnty.js";
4
+ import React, { Children, cloneElement, createContext, isValidElement, use, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { Link, NavLink, Outlet, isRouteErrorResponse, useLocation, useMatches, useNavigate, useRouteError } from "react-router";
7
7
  import "./assets/src/ui/components/HeaderSearch.css.ts.vanilla-KAo_Mlc-.css";
@@ -114,6 +114,11 @@ function formatBuildTime(iso) {
114
114
  * />
115
115
  * ```
116
116
  *
117
+ * @example Trusted HTML opt-in
118
+ * ```tsx
119
+ * <Footer trustedMessageHtml={'Released under the <a href="/license">MIT License</a>.'} />
120
+ * ```
121
+ *
117
122
  * @example Custom content
118
123
  * ```tsx
119
124
  * <Footer>
@@ -173,7 +178,21 @@ function FooterPrimaryLine({ project, sponsor, ardoLink, config }) {
173
178
  }), item] }, i))
174
179
  });
175
180
  }
176
- function ArdoFooter({ children, className, ardoLink = true, message, copyright, project, sponsor, buildTime, buildHash }) {
181
+ function hasFooterContent(value) {
182
+ return value != null && value !== false && value !== "";
183
+ }
184
+ function FooterTextLine({ children, className, trustedHtml }) {
185
+ if ((trustedHtml ?? "") !== "") return /* @__PURE__ */ jsx("p", {
186
+ className,
187
+ dangerouslySetInnerHTML: { __html: trustedHtml ?? "" }
188
+ });
189
+ if (!hasFooterContent(children)) return null;
190
+ return /* @__PURE__ */ jsx("p", {
191
+ className,
192
+ children
193
+ });
194
+ }
195
+ function ArdoFooter({ children, className, ardoLink = true, message, trustedMessageHtml, copyright, trustedCopyrightHtml, project, sponsor, buildTime, buildHash }) {
177
196
  const config = useArdoConfig();
178
197
  const resolvedBuildTime = buildTime ?? config.buildTime;
179
198
  const resolvedBuildHash = buildHash ?? config.buildHash;
@@ -195,13 +214,15 @@ function ArdoFooter({ children, className, ardoLink = true, message, copyright,
195
214
  ardoLink,
196
215
  config
197
216
  }),
198
- (message ?? "") !== "" && /* @__PURE__ */ jsx("p", {
199
- className: "_169q00b7",
200
- dangerouslySetInnerHTML: { __html: message ?? "" }
217
+ /* @__PURE__ */ jsx(FooterTextLine, {
218
+ className: footerMessage,
219
+ trustedHtml: trustedMessageHtml,
220
+ children: message
201
221
  }),
202
- (copyright ?? "") !== "" && /* @__PURE__ */ jsx("p", {
203
- className: "_169q00b8",
204
- dangerouslySetInnerHTML: { __html: copyright ?? "" }
222
+ /* @__PURE__ */ jsx(FooterTextLine, {
223
+ className: footerCopyright,
224
+ trustedHtml: trustedCopyrightHtml,
225
+ children: copyright
205
226
  }),
206
227
  (resolvedBuildTime ?? "") !== "" && /* @__PURE__ */ jsxs("p", {
207
228
  className: "_169q00b9",
@@ -224,6 +245,45 @@ function ArdoFooter({ children, className, ardoLink = true, message, copyright,
224
245
  var inline = "xapf7e0";
225
246
  var trigger = "xapf7e1";
226
247
  //#endregion
248
+ //#region src/ui/components/search-a11y.ts
249
+ function getSearchOptionId(listboxId, resultId, index) {
250
+ return `${listboxId}-option-${toDomIdSegment(resultId)}-${index}`;
251
+ }
252
+ function getActiveSearchOptionId({ getOptionId, isOpen, results, selectedIndex }) {
253
+ if (!isOpen || results.length === 0) return;
254
+ const selectedResult = getResultAtIndex(results, selectedIndex);
255
+ if (selectedResult == null) return;
256
+ return getOptionId(selectedResult.id, selectedIndex);
257
+ }
258
+ function getSearchKeyboardAction({ key, results, selectedIndex }) {
259
+ switch (key) {
260
+ case "ArrowDown": return results.length > 0 ? {
261
+ type: "select-index",
262
+ index: Math.min(selectedIndex + 1, results.length - 1)
263
+ } : { type: "none" };
264
+ case "ArrowUp": return results.length > 0 ? {
265
+ type: "select-index",
266
+ index: Math.max(selectedIndex - 1, 0)
267
+ } : { type: "none" };
268
+ case "Enter": {
269
+ const selectedResult = getResultAtIndex(results, selectedIndex);
270
+ return selectedResult == null ? { type: "none" } : {
271
+ type: "navigate",
272
+ path: selectedResult.path
273
+ };
274
+ }
275
+ case "Escape": return { type: "close" };
276
+ default: return { type: "none" };
277
+ }
278
+ }
279
+ function toDomIdSegment(value) {
280
+ return Array.from(value, (char) => char.charCodeAt(0).toString(36)).join("-");
281
+ }
282
+ function getResultAtIndex(results, selectedIndex) {
283
+ if (selectedIndex < 0 || selectedIndex >= results.length) return;
284
+ return results[selectedIndex];
285
+ }
286
+ //#endregion
227
287
  //#region src/ui/components/search-hooks.ts
228
288
  function useGlobalSearchShortcut(inputRef, setIsOpen) {
229
289
  useEffect(() => {
@@ -311,6 +371,64 @@ function SearchPopover({ anchorRef, children }) {
311
371
  }), document.body);
312
372
  }
313
373
  //#endregion
374
+ //#region src/ui/components/SearchResults.tsx
375
+ function SearchResults({ getOptionId, listboxId, results, selectedIndex, query, onClose }) {
376
+ return /* @__PURE__ */ jsxs(Fragment, { children: [results.length > 0 ? /* @__PURE__ */ jsx("ul", {
377
+ id: listboxId,
378
+ className: searchResults,
379
+ role: "listbox",
380
+ "aria-label": "Search results",
381
+ children: results.map((result, index) => /* @__PURE__ */ jsx("li", {
382
+ role: "presentation",
383
+ children: /* @__PURE__ */ jsxs(Link, {
384
+ id: getOptionId(result.id, index),
385
+ to: result.path,
386
+ role: "option",
387
+ "aria-selected": index === selectedIndex,
388
+ className: [searchResult, index === selectedIndex && "selected"].filter(Boolean).join(" "),
389
+ onClick: onClose,
390
+ children: [/* @__PURE__ */ jsx("span", {
391
+ className: searchResultTitle,
392
+ children: result.title
393
+ }), result.section !== void 0 && /* @__PURE__ */ jsx("span", {
394
+ className: "wxcdiv7",
395
+ children: result.section
396
+ })]
397
+ })
398
+ }, result.id))
399
+ }) : /* @__PURE__ */ jsx("div", {
400
+ id: listboxId,
401
+ role: "listbox",
402
+ "aria-label": "Search results",
403
+ children: /* @__PURE__ */ jsxs("div", {
404
+ className: searchNoResults,
405
+ role: "status",
406
+ "aria-live": "polite",
407
+ children: [/* @__PURE__ */ jsx(ArdoOwlMark, {
408
+ size: 36,
409
+ className: searchNoResultsOwl,
410
+ title: ""
411
+ }), /* @__PURE__ */ jsxs("span", { children: [
412
+ "No results found for \"",
413
+ query,
414
+ "\""
415
+ ] })]
416
+ })
417
+ }), /* @__PURE__ */ jsxs("div", {
418
+ className: searchFooter,
419
+ children: [
420
+ /* @__PURE__ */ jsxs("span", { children: [
421
+ /* @__PURE__ */ jsx("kbd", { children: "↑" }),
422
+ " ",
423
+ /* @__PURE__ */ jsx("kbd", { children: "↓" }),
424
+ " to navigate"
425
+ ] }),
426
+ /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("kbd", { children: "↵" }), " to select"] }),
427
+ /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("kbd", { children: "esc" }), " to close"] })
428
+ ]
429
+ })] });
430
+ }
431
+ //#endregion
314
432
  //#region src/ui/components/Search.tsx
315
433
  function useSearchIndex() {
316
434
  return useMemo(() => {
@@ -348,46 +466,6 @@ function normalizeResults(rawResults) {
348
466
  }];
349
467
  });
350
468
  }
351
- function SearchResults({ results, selectedIndex, query, onClose }) {
352
- return /* @__PURE__ */ jsxs(Fragment, { children: [results.length > 0 ? /* @__PURE__ */ jsx("ul", {
353
- className: searchResults,
354
- children: results.map((result, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Link, {
355
- to: result.path,
356
- className: [searchResult, index === selectedIndex && "selected"].filter(Boolean).join(" "),
357
- onClick: onClose,
358
- children: [/* @__PURE__ */ jsx("span", {
359
- className: searchResultTitle,
360
- children: result.title
361
- }), result.section !== void 0 && /* @__PURE__ */ jsx("span", {
362
- className: "wxcdiv7",
363
- children: result.section
364
- })]
365
- }) }, result.id))
366
- }) : /* @__PURE__ */ jsxs("div", {
367
- className: searchNoResults,
368
- children: [/* @__PURE__ */ jsx(ArdoOwlMark, {
369
- size: 36,
370
- className: searchNoResultsOwl,
371
- title: ""
372
- }), /* @__PURE__ */ jsxs("span", { children: [
373
- "No results found for \"",
374
- query,
375
- "\""
376
- ] })]
377
- }), /* @__PURE__ */ jsxs("div", {
378
- className: searchFooter,
379
- children: [
380
- /* @__PURE__ */ jsxs("span", { children: [
381
- /* @__PURE__ */ jsx("kbd", { children: "↑" }),
382
- " ",
383
- /* @__PURE__ */ jsx("kbd", { children: "↓" }),
384
- " to navigate"
385
- ] }),
386
- /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("kbd", { children: "↵" }), " to select"] }),
387
- /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("kbd", { children: "esc" }), " to close"] })
388
- ]
389
- })] });
390
- }
391
469
  function useSearch(searchIndex) {
392
470
  const [isOpen, setIsOpen] = useState(false);
393
471
  const [query, setQuery] = useState("");
@@ -415,7 +493,7 @@ function useSearch(searchIndex) {
415
493
  search
416
494
  };
417
495
  }
418
- function SearchInput({ inputRef, placeholder, query, hasQuery, onSearch, onKeyDown, onFocus }) {
496
+ function SearchInput({ inputRef, placeholder, query, hasQuery, onSearch, onKeyDown, onFocus, activeOptionId, expanded, listboxId }) {
419
497
  return /* @__PURE__ */ jsxs("div", {
420
498
  className: searchField,
421
499
  children: [
@@ -431,7 +509,13 @@ function SearchInput({ inputRef, placeholder, query, hasQuery, onSearch, onKeyDo
431
509
  },
432
510
  onKeyDown,
433
511
  onFocus,
434
- "aria-label": "Search"
512
+ "aria-label": "Search",
513
+ role: "combobox",
514
+ "aria-autocomplete": "list",
515
+ "aria-expanded": expanded,
516
+ "aria-controls": listboxId,
517
+ "aria-activedescendant": activeOptionId,
518
+ "aria-haspopup": "listbox"
435
519
  }),
436
520
  hasQuery && /* @__PURE__ */ jsx("button", {
437
521
  type: "button",
@@ -451,12 +535,32 @@ function SearchInput({ inputRef, placeholder, query, hasQuery, onSearch, onKeyDo
451
535
  ]
452
536
  });
453
537
  }
538
+ function useSearchA11y({ expanded, results, selectedIndex }) {
539
+ const listboxId = `${useId()}-results`;
540
+ const getOptionId = (resultId, index) => getSearchOptionId(listboxId, resultId, index);
541
+ return {
542
+ activeOptionId: getActiveSearchOptionId({
543
+ getOptionId,
544
+ isOpen: expanded,
545
+ results,
546
+ selectedIndex
547
+ }),
548
+ getOptionId,
549
+ listboxId
550
+ };
551
+ }
454
552
  function ArdoSearch({ placeholder = "Search...", autoFocus = false }) {
455
553
  const navigate = useNavigate();
456
554
  const containerRef = useRef(null);
457
555
  const inputRef = useRef(null);
458
556
  const state = useSearch(useSearchIndex());
459
557
  const hasQuery = state.query.trim().length > 0;
558
+ const expanded = state.isOpen && hasQuery;
559
+ const searchA11y = useSearchA11y({
560
+ expanded,
561
+ results: state.results,
562
+ selectedIndex: state.selectedIndex
563
+ });
460
564
  useEffect(() => {
461
565
  if (autoFocus) inputRef.current?.focus();
462
566
  }, [autoFocus]);
@@ -468,39 +572,34 @@ function ArdoSearch({ placeholder = "Search...", autoFocus = false }) {
468
572
  setIsOpen: state.setIsOpen
469
573
  });
470
574
  const handleKeyDown = (e) => {
471
- switch (e.key) {
472
- case "ArrowDown":
473
- if (state.results.length > 0) {
474
- e.preventDefault();
475
- state.setSelectedIndex((p) => Math.min(p + 1, state.results.length - 1));
476
- }
477
- break;
478
- case "ArrowUp":
479
- if (state.results.length > 0) {
575
+ const action = getSearchKeyboardAction({
576
+ key: e.key,
577
+ results: state.results,
578
+ selectedIndex: state.selectedIndex
579
+ });
580
+ switch (action.type) {
581
+ case "select-index":
582
+ if (state.isOpen) {
480
583
  e.preventDefault();
481
- state.setSelectedIndex((p) => Math.max(p - 1, 0));
584
+ state.setSelectedIndex(action.index);
482
585
  }
483
586
  break;
484
- case "Enter": {
485
- const p = state.results[state.selectedIndex]?.path;
486
- if (typeof p === "string") {
487
- e.preventDefault();
488
- navigate(p);
489
- state.setIsOpen(false);
490
- }
587
+ case "navigate":
588
+ e.preventDefault();
589
+ navigate(action.path);
590
+ state.setIsOpen(false);
491
591
  break;
492
- }
493
- case "Escape":
592
+ case "close":
494
593
  state.setIsOpen(false);
495
594
  inputRef.current?.blur();
496
595
  break;
497
- default: break;
596
+ case "none": break;
498
597
  }
499
598
  };
500
599
  return /* @__PURE__ */ jsxs("div", {
501
600
  className: search,
502
601
  ref: containerRef,
503
- "data-expanded": state.isOpen || hasQuery ? "true" : "false",
602
+ "data-expanded": expanded ? "true" : "false",
504
603
  onMouseDown: () => inputRef.current?.focus(),
505
604
  children: [/* @__PURE__ */ jsx(SearchInput, {
506
605
  inputRef,
@@ -511,10 +610,15 @@ function ArdoSearch({ placeholder = "Search...", autoFocus = false }) {
511
610
  onKeyDown: handleKeyDown,
512
611
  onFocus: () => {
513
612
  if (hasQuery) state.setIsOpen(true);
514
- }
515
- }), state.isOpen && hasQuery && /* @__PURE__ */ jsx(SearchPopover, {
613
+ },
614
+ activeOptionId: searchA11y.activeOptionId,
615
+ expanded,
616
+ listboxId: searchA11y.listboxId
617
+ }), expanded && /* @__PURE__ */ jsx(SearchPopover, {
516
618
  anchorRef: containerRef,
517
619
  children: /* @__PURE__ */ jsx(SearchResults, {
620
+ getOptionId: searchA11y.getOptionId,
621
+ listboxId: searchA11y.listboxId,
518
622
  results: state.results,
519
623
  selectedIndex: state.selectedIndex,
520
624
  query: state.query,
@@ -667,6 +771,131 @@ var mobilePanelClose = "qjc2r5e";
667
771
  var mobilePanelHeader = "qjc2r5d";
668
772
  var mobilePanelSidebar = "qjc2r5g";
669
773
  //#endregion
774
+ //#region src/ui/mobile-drawer-a11y.ts
775
+ function getTrappedFocusTarget({ activeElement, focusableElements, shiftKey }) {
776
+ const firstElement = focusableElements[0];
777
+ const lastElement = focusableElements.at(-1);
778
+ if (firstElement == null || lastElement == null) return;
779
+ if (shiftKey && activeElement === firstElement) return lastElement;
780
+ if (!shiftKey && activeElement === lastElement) return firstElement;
781
+ }
782
+ //#endregion
783
+ //#region src/ui/MobileSlidePanel.tsx
784
+ const FOCUSABLE_SELECTOR = [
785
+ "a[href]",
786
+ "button:not([disabled])",
787
+ "input:not([disabled])",
788
+ "select:not([disabled])",
789
+ "textarea:not([disabled])",
790
+ "[tabindex]:not([tabindex=\"-1\"])"
791
+ ].join(",");
792
+ function MobileSlidePanel({ logo: logo$2, title, nav, themeToggle, triggerRef, children, onClose }) {
793
+ const panelRef = useRef(null);
794
+ useMobilePanelFocus({
795
+ onClose,
796
+ panelRef,
797
+ triggerRef
798
+ });
799
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
800
+ className: mobileBackdrop,
801
+ "data-open": "true",
802
+ onClick: onClose
803
+ }), /* @__PURE__ */ jsxs("div", {
804
+ ref: panelRef,
805
+ className: mobilePanel,
806
+ "data-open": "true",
807
+ role: "dialog",
808
+ "aria-modal": "true",
809
+ "aria-label": title === "" ? "Navigation menu" : `${title} navigation menu`,
810
+ tabIndex: -1,
811
+ children: [
812
+ /* @__PURE__ */ jsxs("div", {
813
+ className: mobilePanelHeader,
814
+ children: [/* @__PURE__ */ jsx(Link, {
815
+ to: "/",
816
+ className: logoLink,
817
+ onClick: onClose,
818
+ children: logo$2 != null && /* @__PURE__ */ jsx("img", {
819
+ src: typeof logo$2 === "string" ? logo$2 : logo$2.light,
820
+ alt: title,
821
+ className: "qjc2r56"
822
+ })
823
+ }), /* @__PURE__ */ jsxs("div", {
824
+ style: {
825
+ display: "flex",
826
+ alignItems: "center",
827
+ gap: "0.5rem"
828
+ },
829
+ children: [themeToggle && /* @__PURE__ */ jsx(ArdoThemeToggle, {}), /* @__PURE__ */ jsx("button", {
830
+ type: "button",
831
+ className: mobilePanelClose,
832
+ onClick: onClose,
833
+ "aria-label": "Close menu",
834
+ children: /* @__PURE__ */ jsx(XIcon, { size: 20 })
835
+ })]
836
+ })]
837
+ }),
838
+ nav != null && /* @__PURE__ */ jsx("div", {
839
+ className: "qjc2r5f",
840
+ onClickCapture: handleLinkClick(onClose),
841
+ children: nav
842
+ }),
843
+ /* @__PURE__ */ jsx("div", {
844
+ className: mobilePanelSidebar,
845
+ onClickCapture: handleLinkClick(onClose),
846
+ children
847
+ })
848
+ ]
849
+ })] });
850
+ }
851
+ function useMobilePanelFocus({ onClose, panelRef, triggerRef }) {
852
+ useEffect(() => {
853
+ const panel = panelRef.current;
854
+ if (panel == null) return;
855
+ const triggerElement = triggerRef.current;
856
+ focusInitialPanelElement(panel);
857
+ const handleKeyDown = (event) => {
858
+ if (event.key === "Escape") {
859
+ event.preventDefault();
860
+ onClose();
861
+ return;
862
+ }
863
+ if (event.key === "Tab") trapPanelFocus(event, panel);
864
+ };
865
+ document.addEventListener("keydown", handleKeyDown);
866
+ return () => {
867
+ document.removeEventListener("keydown", handleKeyDown);
868
+ triggerElement?.focus();
869
+ };
870
+ }, [
871
+ onClose,
872
+ panelRef,
873
+ triggerRef
874
+ ]);
875
+ }
876
+ function focusInitialPanelElement(panel) {
877
+ (getFocusablePanelElements(panel)[0] ?? panel).focus();
878
+ }
879
+ function trapPanelFocus(event, panel) {
880
+ const focusableElements = getFocusablePanelElements(panel);
881
+ const target = getTrappedFocusTarget({
882
+ activeElement: document.activeElement instanceof HTMLElement ? document.activeElement : null,
883
+ focusableElements,
884
+ shiftKey: event.shiftKey
885
+ });
886
+ if (target == null) return;
887
+ event.preventDefault();
888
+ target.focus();
889
+ }
890
+ function getFocusablePanelElements(panel) {
891
+ return [...panel.querySelectorAll(FOCUSABLE_SELECTOR)].filter((element) => !element.hasAttribute("disabled") && element.getAttribute("aria-hidden") !== "true");
892
+ }
893
+ function handleLinkClick(onClose) {
894
+ return (event) => {
895
+ if (event.target instanceof HTMLElement && event.target.closest("a") !== null) onClose();
896
+ };
897
+ }
898
+ //#endregion
670
899
  //#region src/ui/Header.tsx
671
900
  /**
672
901
  * Mobile menu open state — resets on navigation and locks body scroll
@@ -686,14 +915,18 @@ function useMobileMenu() {
686
915
  }, [open]);
687
916
  return [open, setOpen];
688
917
  }
689
- function ArdoHeader({ logo: logo$2, title, nav, actions, search = true, searchPlaceholder, themeToggle = true, mobileMenuContent, className }) {
918
+ function ArdoHeader({ logo: logo$1, title, nav, actions, search = true, searchPlaceholder, themeToggle = true, mobileMenuContent, className }) {
690
919
  const config = useArdoConfig();
691
920
  const [mobileMenuOpen, setMobileMenuOpen] = useMobileMenu();
692
- const resolvedLogo = logo$2;
921
+ const mobileMenuButtonRef = useRef(null);
922
+ const resolvedLogo = logo$1;
693
923
  const resolvedTitle = title ?? config.title;
694
924
  const hasLogo = resolvedLogo !== void 0;
695
925
  const hasTitle = resolvedTitle !== "";
696
926
  const hasMobileMenu = mobileMenuContent != null;
927
+ const closeMobileMenu = useCallback(() => {
928
+ setMobileMenuOpen(false);
929
+ }, [setMobileMenuOpen]);
697
930
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("header", {
698
931
  className: className ?? "qjc2r50",
699
932
  children: /* @__PURE__ */ jsxs("div", {
@@ -702,6 +935,7 @@ function ArdoHeader({ logo: logo$2, title, nav, actions, search = true, searchPl
702
935
  /* @__PURE__ */ jsxs("div", {
703
936
  className: headerLeft,
704
937
  children: [hasMobileMenu && /* @__PURE__ */ jsx("button", {
938
+ ref: mobileMenuButtonRef,
705
939
  type: "button",
706
940
  className: "qjc2r58",
707
941
  onClick: () => {
@@ -747,72 +981,16 @@ function ArdoHeader({ logo: logo$2, title, nav, actions, search = true, searchPl
747
981
  })
748
982
  ]
749
983
  })
750
- }), hasMobileMenu && /* @__PURE__ */ jsx(MobileSlidePanel, {
751
- isOpen: mobileMenuOpen,
984
+ }), hasMobileMenu && mobileMenuOpen && /* @__PURE__ */ jsx(MobileSlidePanel, {
752
985
  logo: resolvedLogo,
753
986
  title: resolvedTitle,
754
987
  nav,
755
988
  themeToggle,
756
- onClose: () => {
757
- setMobileMenuOpen(false);
758
- },
989
+ triggerRef: mobileMenuButtonRef,
990
+ onClose: closeMobileMenu,
759
991
  children: mobileMenuContent
760
992
  })] });
761
993
  }
762
- function MobileSlidePanel({ isOpen, logo: logo$1, title, nav, themeToggle, children, onClose }) {
763
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
764
- className: mobileBackdrop,
765
- "data-open": isOpen,
766
- onClick: onClose
767
- }), /* @__PURE__ */ jsxs("div", {
768
- className: mobilePanel,
769
- "data-open": isOpen,
770
- "aria-hidden": !isOpen,
771
- children: [
772
- /* @__PURE__ */ jsxs("div", {
773
- className: mobilePanelHeader,
774
- children: [/* @__PURE__ */ jsx(Link, {
775
- to: "/",
776
- className: logoLink,
777
- onClick: onClose,
778
- children: logo$1 != null && /* @__PURE__ */ jsx("img", {
779
- src: typeof logo$1 === "string" ? logo$1 : logo$1.light,
780
- alt: title,
781
- className: "qjc2r56"
782
- })
783
- }), /* @__PURE__ */ jsxs("div", {
784
- style: {
785
- display: "flex",
786
- alignItems: "center",
787
- gap: "0.5rem"
788
- },
789
- children: [themeToggle && /* @__PURE__ */ jsx(ArdoThemeToggle, {}), /* @__PURE__ */ jsx("button", {
790
- type: "button",
791
- className: mobilePanelClose,
792
- onClick: onClose,
793
- "aria-label": "Close menu",
794
- children: /* @__PURE__ */ jsx(XIcon, { size: 20 })
795
- })]
796
- })]
797
- }),
798
- nav != null && /* @__PURE__ */ jsx("div", {
799
- className: "qjc2r5f",
800
- onClickCapture: handleLinkClick(onClose),
801
- children: nav
802
- }),
803
- /* @__PURE__ */ jsx("div", {
804
- className: mobilePanelSidebar,
805
- onClickCapture: handleLinkClick(onClose),
806
- children
807
- })
808
- ]
809
- })] });
810
- }
811
- function handleLinkClick(onClose) {
812
- return (event) => {
813
- if (event.target instanceof HTMLElement && event.target.closest("a") !== null) onClose();
814
- };
815
- }
816
994
  function ArdoSocialLink({ href, icon, ariaLabel, className }) {
817
995
  return /* @__PURE__ */ jsx("a", {
818
996
  href,
@@ -1760,4 +1938,4 @@ function ArdoNavLink({ to, href, children, className, activeMatch: _activeMatch
1760
1938
  //#endregion
1761
1939
  export { ArdoOwlMark as _, ArdoHero as a, ArdoRoot as c, ArdoSidebarLink as d, ArdoHeader as f, ArdoFooter as g, ArdoSearch as h, ArdoErrorBoundary as i, ArdoSidebar as l, ArdoThemeToggle as m, ArdoNavLink as n, ArdoFeatureCard as o, ArdoSocialLink as p, ArdoHomePage as r, ArdoFeatures as s, ArdoNav as t, ArdoSidebarGroup as u };
1762
1940
 
1763
- //# sourceMappingURL=ui-B6X8gAvz.js.map
1941
+ //# sourceMappingURL=ui-mBEFGR-s.js.map