boltdocs 2.8.4 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/bin/boltdocs.js +7 -6
  2. package/dist/{banner-DFACg5Dy.d.cts → banner-C_xkGxzI.d.cts} +1 -1
  3. package/dist/{banner-BLTQUA2O.d.ts → banner-D2NddBfF.d.ts} +1 -1
  4. package/dist/cache-C2PiGyg8.cjs +6 -0
  5. package/dist/cache-DFW4R3S0.mjs +6 -0
  6. package/dist/chunk-Ds5LZdWN.cjs +6 -0
  7. package/dist/client/index.cjs +464 -279
  8. package/dist/client/index.d.cts +74 -8
  9. package/dist/client/index.d.ts +74 -8
  10. package/dist/client/index.js +463 -280
  11. package/dist/client/mdx.cjs +1 -1
  12. package/dist/client/mdx.js +1 -1
  13. package/dist/client/primitives.cjs +4 -4
  14. package/dist/client/primitives.d.cts +1 -1
  15. package/dist/client/primitives.d.ts +1 -1
  16. package/dist/client/primitives.js +4 -4
  17. package/dist/client/theme/neutral.css +5 -3
  18. package/dist/client/theme/reset.css +19 -0
  19. package/dist/{docs-layout-CVCf3xkZ.cjs → docs-layout-3TpAt6MB.cjs} +302 -238
  20. package/dist/{docs-layout-BBv6wILp.js → docs-layout-6FQ8__ok.js} +303 -239
  21. package/dist/doctor-BY1tSmQd.mjs +34 -0
  22. package/dist/{doctor-DT4jh07O.cjs → doctor-Bknon8f5.cjs} +1 -1
  23. package/dist/doctor-DcFLi4t1.cjs +34 -0
  24. package/dist/{doctor-BGPdO1Ah.mjs → doctor-wsPTzt8Y.mjs} +1 -1
  25. package/dist/{generator-CC2yHzhZ.cjs → generator-BEIF6MOx.cjs} +2 -2
  26. package/dist/{generator-BHCrLU6h.mjs → generator-DILs0EFN.mjs} +1 -1
  27. package/dist/handler-B31cxNw0.mjs +6 -0
  28. package/dist/handler-DfhH9D1b.mjs +28 -0
  29. package/dist/handler-Djy0HfsI.cjs +28 -0
  30. package/dist/{icons-dev-C8sFxeBk.cjs → icons-dev-BryEFkJu.cjs} +90 -4
  31. package/dist/{icons-dev-CFcv5Nb8.js → icons-dev-kJyGWNeg.js} +73 -5
  32. package/dist/{image-DCMxxidi.cjs → image-B9YA_oQ_.cjs} +17 -9
  33. package/dist/{image-DufDOid8.js → image-CjYu9Eml.js} +17 -9
  34. package/dist/{mdx-Bto8CvGl.js → mdx-Cy_c2zio.js} +116 -33
  35. package/dist/{mdx-BH9kHiF9.cjs → mdx-WXjAMS8Y.cjs} +121 -32
  36. package/dist/{meta-loader-Cv9O0Pzl.cjs → meta-loader-D0V6oOQt.cjs} +1 -1
  37. package/dist/node/cli-entry.cjs +3 -3
  38. package/dist/node/cli-entry.mjs +3 -3
  39. package/dist/node/index.cjs +1 -1
  40. package/dist/node/index.d.cts +368 -24
  41. package/dist/node/index.d.mts +369 -24
  42. package/dist/node/index.mjs +1 -1
  43. package/dist/node/routes/worker.cjs +1 -1
  44. package/dist/node/routes/worker.mjs +1 -1
  45. package/dist/node-DV-EPtMU.mjs +160 -0
  46. package/dist/node-vOVNRABe.cjs +160 -0
  47. package/dist/{package-DlgexgpQ.mjs → package-BK_82QLX.mjs} +1 -1
  48. package/dist/{package-CwNjt3Sd.cjs → package-OxKE0p6j.cjs} +1 -1
  49. package/dist/{parser-B7-6PyQz.cjs → parser-C2Pw18LY.cjs} +1 -1
  50. package/dist/{parser-BzB-zCkF.cjs → parser-DJoEF96Y.cjs} +1 -1
  51. package/dist/{parser-9LqyiEnB.mjs → parser-DScG_PTs.mjs} +1 -1
  52. package/dist/routes--LYHSHT7.cjs +6 -0
  53. package/dist/routes-BAsfsn_e.mjs +6 -0
  54. package/dist/routes-C6Kr2HFN.cjs +6 -0
  55. package/dist/routes-CKgGAy8P.mjs +6 -0
  56. package/dist/{search-dialog-Cvux-qSk.cjs → search-dialog-BhXg05Ri.cjs} +1 -1
  57. package/dist/{search-dialog-CmHYOt18.cjs → search-dialog-BsAiQgnx.cjs} +2 -2
  58. package/dist/{search-dialog-jHZ21G4i.js → search-dialog-CaOO1Q4i.js} +2 -2
  59. package/dist/{search-dialog-Cr7SC8Xt.cjs → search-dialog-CzwvKoLW.cjs} +1 -1
  60. package/dist/{search-dialog-DBNaG5At.js → search-dialog-DRNJsxOv.js} +1 -1
  61. package/dist/{search-dialog-e86WPpck.js → search-dialog-DaR66wl0.js} +1 -1
  62. package/dist/shiki-adapter-CUtj-xKv.cjs +6 -0
  63. package/dist/shiki-adapter-EZptJAmW.mjs +6 -0
  64. package/dist/shiki-adapter-F-P-lDon.mjs +6 -0
  65. package/dist/shiki-adapter-PQBl32AN.cjs +6 -0
  66. package/dist/{sidebar-kQBonN82.d.ts → sidebar-BtJyWHy4.d.cts} +42 -5
  67. package/dist/{sidebar---RgTmZD.d.cts → sidebar-C4fuRYYS.d.ts} +42 -5
  68. package/dist/{utils-BYITg7T5.mjs → utils-DC4Wq-S2.mjs} +1 -1
  69. package/dist/{utils-Cjmx1hhk.cjs → utils-wHud9ny5.cjs} +1 -1
  70. package/dist/worker-pool-BS1LX67P.cjs +6 -0
  71. package/dist/worker-pool-DRv8ef4z.mjs +6 -0
  72. package/package.json +6 -3
  73. package/src/shared/types.ts +36 -3
  74. package/dist/cache-BMUyNiiA.mjs +0 -6
  75. package/dist/cache-CKm45d2w.cjs +0 -6
  76. package/dist/chunk-CU-zTemE.cjs +0 -6
  77. package/dist/doctor-Dnv77Lfx.mjs +0 -28
  78. package/dist/doctor-VBPlQf7d.cjs +0 -28
  79. package/dist/node-JM9bx0mO.cjs +0 -160
  80. package/dist/node-q-Y2mdLX.mjs +0 -160
  81. package/dist/routes-3sAccArX.cjs +0 -6
  82. package/dist/routes-DZpXqq1p.mjs +0 -6
  83. package/dist/routes-DjoxNeef.mjs +0 -6
  84. package/dist/routes-DpJnb2VS.cjs +0 -6
  85. package/dist/worker-pool-DXLyeHJg.cjs +0 -6
  86. package/dist/worker-pool-Kn4F9gDq.mjs +0 -6
  87. /package/dist/{meta-loader-DzwDFtdT.mjs → meta-loader-By22EnYa.mjs} +0 -0
@@ -3,7 +3,7 @@
3
3
  * Copyright (c) 2026 Jesus Alcala
4
4
  * Licensed under the MIT License.
5
5
  */
6
- import { A as ChevronRight, D as Check, Q as icons_exports, S as normalizePath, _ as Link$1, b as useLocalizedTo, et as useConfig, k as ChevronLeft, y as cn } from "./icons-dev-CFcv5Nb8.js";
6
+ import { A as ChevronRight, D as Check, S as normalizePath, _ as Link$1, b as useLocalizedTo, k as ChevronLeft, rt as useConfig, tt as icons_exports, y as cn } from "./icons-dev-kJyGWNeg.js";
7
7
  import { Children, createContext, use, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
8
8
  import { useLocation } from "react-router-dom";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -242,6 +242,235 @@ Tabs.Item = TabsItem;
242
242
  Tabs.Content = TabsContent;
243
243
  Tabs.Indicator = TabsIndicator;
244
244
 
245
+ //#endregion
246
+ //#region src/client/hooks/use-sidebar.ts
247
+ const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, " ");
248
+ const getCleanDirectoryMeta = (directoryMeta) => {
249
+ const meta = {};
250
+ if (!directoryMeta) return meta;
251
+ for (const [key, value] of Object.entries(directoryMeta)) {
252
+ const cleanKey = key.split("/").filter((part) => !part.startsWith("(") || !part.endsWith(")")).map((part) => part.replace(/^\d+-/, "")).join("/");
253
+ meta[cleanKey === "" ? "." : cleanKey] = value;
254
+ }
255
+ return meta;
256
+ };
257
+ const getOrCreateNode = (parts, rootMap, directoryMeta) => {
258
+ let currentMap = rootMap;
259
+ let parentPath = "";
260
+ let lastNode;
261
+ for (const segment of parts) {
262
+ const currentRelPath = parentPath ? `${parentPath}/${segment}` : segment;
263
+ if (!currentMap.has(segment)) {
264
+ const meta = directoryMeta[currentRelPath] || {};
265
+ const newNode = {
266
+ path: "#",
267
+ title: meta.title || capitalize(segment),
268
+ componentPath: "",
269
+ filePath: "",
270
+ icon: meta.icon,
271
+ groupPosition: typeof meta.order === "number" ? meta.order : 999,
272
+ collapsible: meta.collapsible,
273
+ collapsed: meta.collapsed,
274
+ hasCustomTitle: meta.title !== void 0,
275
+ subRoutes: [],
276
+ childrenMap: /* @__PURE__ */ new Map()
277
+ };
278
+ currentMap.set(segment, newNode);
279
+ }
280
+ lastNode = currentMap.get(segment);
281
+ currentMap = lastNode.childrenMap;
282
+ parentPath = currentRelPath;
283
+ }
284
+ return lastNode;
285
+ };
286
+ const getRoutePosition = (r) => r.sidebarPosition ?? r.order ?? 999;
287
+ const getNodePosition = (n) => n.sidebarPosition ?? n.groupPosition ?? 999;
288
+ const finalizeTree = (nodes) => {
289
+ return nodes.map((node) => {
290
+ if (node.childrenMap && node.childrenMap.size > 0) {
291
+ const childDirs = Array.from(node.childrenMap.values());
292
+ node.subRoutes = [...node.subRoutes || [], ...childDirs];
293
+ }
294
+ const { childrenMap, ...restNode } = node;
295
+ if (restNode.subRoutes && restNode.subRoutes.length > 0) restNode.subRoutes = finalizeTree(restNode.subRoutes);
296
+ return restNode;
297
+ }).sort((a, b) => {
298
+ const posA = getNodePosition(a);
299
+ const posB = getNodePosition(b);
300
+ return posA !== posB ? posA - posB : a.title.localeCompare(b.title);
301
+ });
302
+ };
303
+ function useSidebar(routes) {
304
+ const config = useConfig();
305
+ const { pathname } = useLocation();
306
+ return useMemo(() => {
307
+ const currentPath = normalizePath(pathname);
308
+ const activeRoute = routes.find((r) => normalizePath(r.path) === currentPath);
309
+ const activeTabId = activeRoute?.tab?.toLowerCase();
310
+ const filteredRoutes = routes.filter((r) => !r.collection && !r.sidebarHidden && !r.fallback).filter((r) => !activeTabId || r.tab?.toLowerCase() === activeTabId).sort((a, b) => getRoutePosition(a) - getRoutePosition(b));
311
+ const directoryMeta = getCleanDirectoryMeta(config.directoryMeta);
312
+ const rootNodesMap = /* @__PURE__ */ new Map();
313
+ const ungrouped = [];
314
+ for (const route of filteredRoutes) {
315
+ const parts = route.slugParts || [];
316
+ const isIndex = /^index\.mdx?$/.test(route.filePath.split("/").pop() || "");
317
+ if (parts.length === 0) {
318
+ if (route.filePath) ungrouped.push(route);
319
+ continue;
320
+ }
321
+ const containerNode = getOrCreateNode(parts, rootNodesMap, directoryMeta);
322
+ if (isIndex) Object.assign(containerNode, {
323
+ path: route.path,
324
+ title: containerNode.hasCustomTitle ? containerNode.title : route.title || containerNode.title,
325
+ icon: route.icon || containerNode.icon,
326
+ badge: route.badge,
327
+ sidebarPosition: route.sidebarPosition,
328
+ frontmatter: route.frontmatter,
329
+ filePath: route.filePath
330
+ });
331
+ else containerNode.subRoutes.push(route);
332
+ }
333
+ const finalizedTopNodes = finalizeTree(Array.from(rootNodesMap.values()));
334
+ const groups = [];
335
+ for (const node of finalizedTopNodes) if (node.subRoutes && node.subRoutes.length > 0) groups.push({
336
+ slug: node.title.toLowerCase().replace(/\s+/g, "-"),
337
+ title: node.title,
338
+ icon: node.icon,
339
+ path: node.path,
340
+ filePath: node.filePath,
341
+ routes: node.subRoutes,
342
+ sidebarPosition: node.sidebarPosition ?? node.groupPosition ?? 999,
343
+ collapsible: node.collapsible,
344
+ collapsed: node.collapsed
345
+ });
346
+ else ungrouped.push(node);
347
+ return {
348
+ groups,
349
+ ungrouped: finalizeTree(ungrouped),
350
+ activeRoute,
351
+ activePath: currentPath,
352
+ config
353
+ };
354
+ }, [
355
+ routes,
356
+ config,
357
+ pathname
358
+ ]);
359
+ }
360
+
361
+ //#endregion
362
+ //#region src/client/hooks/use-location.ts
363
+ const useLocation$1 = () => {
364
+ return useLocation();
365
+ };
366
+
367
+ //#endregion
368
+ //#region src/client/hooks/use-search-highlight.ts
369
+ /**
370
+ * Hook to highlight search terms based on the 'hl' query parameter.
371
+ */
372
+ function useSearchHighlight(containerSelector = ".boltdocs-page") {
373
+ const { search } = useLocation$1();
374
+ const query = new URLSearchParams(search).get("hl");
375
+ useEffect(() => {
376
+ if (!query) {
377
+ clearHighlights(containerSelector);
378
+ return;
379
+ }
380
+ const container = document.querySelector(containerSelector);
381
+ if (!container) return;
382
+ let rafId;
383
+ const observer = new MutationObserver((mutations) => {
384
+ if (mutations.some((m) => {
385
+ const addedNodes = Array.from(m.addedNodes);
386
+ const removedNodes = Array.from(m.removedNodes);
387
+ return addedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight"))) || removedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight")));
388
+ })) run();
389
+ });
390
+ function run() {
391
+ cancelAnimationFrame(rafId);
392
+ rafId = requestAnimationFrame(() => {
393
+ observer.disconnect();
394
+ clearHighlights(containerSelector);
395
+ const terms = query.split(/\s+/).map((t) => t.trim()).filter((t) => t.length >= 2);
396
+ if (terms.length > 0) highlightTerms(container, terms);
397
+ observer.observe(container, {
398
+ childList: true,
399
+ subtree: true
400
+ });
401
+ });
402
+ }
403
+ run();
404
+ return () => {
405
+ cancelAnimationFrame(rafId);
406
+ observer.disconnect();
407
+ clearHighlights(containerSelector);
408
+ };
409
+ }, [
410
+ query,
411
+ search,
412
+ containerSelector
413
+ ]);
414
+ }
415
+ function clearHighlights(selector) {
416
+ document.querySelectorAll(`${selector} mark[data-search-highlight]`).forEach((mark) => {
417
+ try {
418
+ const parent = mark.parentNode;
419
+ if (parent && parent.contains(mark)) {
420
+ const text = mark.textContent || "";
421
+ parent.replaceChild(document.createTextNode(text), mark);
422
+ }
423
+ } catch (e) {}
424
+ });
425
+ }
426
+ function highlightTerms(container, terms) {
427
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
428
+ const parent = node.parentElement;
429
+ if (parent && (parent.tagName === "SCRIPT" || parent.tagName === "STYLE" || parent.tagName === "MARK" || parent.closest("pre") || parent.closest("code"))) return NodeFilter.FILTER_REJECT;
430
+ return NodeFilter.FILTER_ACCEPT;
431
+ } });
432
+ const nodes = [];
433
+ let node;
434
+ while (node = walker.nextNode()) nodes.push(node);
435
+ const accentMap = {
436
+ a: "[aáàäâã]",
437
+ e: "[eéèëê]",
438
+ i: "[iíìïî]",
439
+ o: "[oóòöôõ]",
440
+ u: "[uúùüû]",
441
+ n: "[nñ]",
442
+ c: "[cç]"
443
+ };
444
+ const prepareRegex = (term) => {
445
+ let pattern = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
446
+ pattern = pattern.split("").map((char) => {
447
+ return accentMap[char.toLowerCase()] || char;
448
+ }).join("");
449
+ return pattern;
450
+ };
451
+ const combinedPattern = terms.map(prepareRegex).join("|");
452
+ const regex = new RegExp(`(${combinedPattern})`, "gi");
453
+ const matchRegexes = terms.map((term) => {
454
+ const p = prepareRegex(term);
455
+ return new RegExp(`^${p}$`, "i");
456
+ });
457
+ nodes.forEach((textNode) => {
458
+ const text = textNode.textContent;
459
+ if (text && regex.test(text)) {
460
+ const fragment = document.createDocumentFragment();
461
+ text.split(regex).forEach((part) => {
462
+ if (matchRegexes.some((rx) => rx.test(part))) {
463
+ const mark = document.createElement("mark");
464
+ mark.textContent = part;
465
+ mark.setAttribute("data-search-highlight", "true");
466
+ fragment.appendChild(mark);
467
+ } else if (part) fragment.appendChild(document.createTextNode(part));
468
+ });
469
+ if (textNode.parentNode) textNode.parentNode.replaceChild(fragment, textNode);
470
+ }
471
+ });
472
+ }
473
+
245
474
  //#endregion
246
475
  //#region src/client/components/primitives/helpers/observer.ts
247
476
  function getItemId(url) {
@@ -617,116 +846,6 @@ PageNav.Title = PageNavTitle;
617
846
  PageNav.Description = PageNavDescription;
618
847
  PageNav.Icon = PageNavIcon;
619
848
 
620
- //#endregion
621
- //#region src/client/hooks/use-sidebar.ts
622
- const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, " ");
623
- const getCleanDirectoryMeta = (directoryMeta) => {
624
- const meta = {};
625
- if (!directoryMeta) return meta;
626
- for (const [key, value] of Object.entries(directoryMeta)) {
627
- const cleanKey = key.split("/").filter((part) => !part.startsWith("(") || !part.endsWith(")")).map((part) => part.replace(/^\d+-/, "")).join("/");
628
- meta[cleanKey === "" ? "." : cleanKey] = value;
629
- }
630
- return meta;
631
- };
632
- const getOrCreateNode = (parts, rootMap, directoryMeta) => {
633
- let currentMap = rootMap;
634
- let parentPath = "";
635
- let lastNode;
636
- for (const segment of parts) {
637
- const currentRelPath = parentPath ? `${parentPath}/${segment}` : segment;
638
- if (!currentMap.has(segment)) {
639
- const meta = directoryMeta[currentRelPath] || {};
640
- const newNode = {
641
- path: "#",
642
- title: meta.title || capitalize(segment),
643
- componentPath: "",
644
- filePath: "",
645
- icon: meta.icon,
646
- groupPosition: typeof meta.order === "number" ? meta.order : 999,
647
- subRoutes: [],
648
- childrenMap: /* @__PURE__ */ new Map()
649
- };
650
- currentMap.set(segment, newNode);
651
- }
652
- lastNode = currentMap.get(segment);
653
- currentMap = lastNode.childrenMap;
654
- parentPath = currentRelPath;
655
- }
656
- return lastNode;
657
- };
658
- const getRoutePosition = (r) => r.sidebarPosition ?? r.order ?? 999;
659
- const getNodePosition = (n) => n.sidebarPosition ?? n.groupPosition ?? 999;
660
- const finalizeTree = (nodes) => {
661
- return nodes.map((node) => {
662
- if (node.childrenMap && node.childrenMap.size > 0) {
663
- const childDirs = Array.from(node.childrenMap.values());
664
- node.subRoutes = [...node.subRoutes || [], ...childDirs];
665
- }
666
- const { childrenMap, ...restNode } = node;
667
- if (restNode.subRoutes && restNode.subRoutes.length > 0) restNode.subRoutes = finalizeTree(restNode.subRoutes);
668
- return restNode;
669
- }).sort((a, b) => {
670
- const posA = getNodePosition(a);
671
- const posB = getNodePosition(b);
672
- return posA !== posB ? posA - posB : a.title.localeCompare(b.title);
673
- });
674
- };
675
- function useSidebar(routes) {
676
- const config = useConfig();
677
- const { pathname } = useLocation();
678
- return useMemo(() => {
679
- const currentPath = normalizePath(pathname);
680
- const activeRoute = routes.find((r) => normalizePath(r.path) === currentPath);
681
- const activeTabId = activeRoute?.tab?.toLowerCase();
682
- const filteredRoutes = routes.filter((r) => !r.collection && !r.sidebarHidden && !r.fallback).filter((r) => !activeTabId || r.tab?.toLowerCase() === activeTabId).sort((a, b) => getRoutePosition(a) - getRoutePosition(b));
683
- const directoryMeta = getCleanDirectoryMeta(config.directoryMeta);
684
- const rootNodesMap = /* @__PURE__ */ new Map();
685
- const ungrouped = [];
686
- for (const route of filteredRoutes) {
687
- const parts = route.slugParts || [];
688
- const isIndex = /^index\.mdx?$/.test(route.filePath.split("/").pop() || "");
689
- if (parts.length === 0) {
690
- if (route.filePath) ungrouped.push(route);
691
- continue;
692
- }
693
- const containerNode = getOrCreateNode(parts, rootNodesMap, directoryMeta);
694
- if (isIndex) Object.assign(containerNode, {
695
- path: route.path,
696
- title: route.title || containerNode.title,
697
- icon: route.icon || containerNode.icon,
698
- badge: route.badge,
699
- sidebarPosition: route.sidebarPosition,
700
- frontmatter: route.frontmatter,
701
- filePath: route.filePath
702
- });
703
- else containerNode.subRoutes.push(route);
704
- }
705
- const finalizedTopNodes = finalizeTree(Array.from(rootNodesMap.values()));
706
- const groups = [];
707
- for (const node of finalizedTopNodes) if (node.subRoutes && node.subRoutes.length > 0) groups.push({
708
- slug: node.title.toLowerCase().replace(/\s+/g, "-"),
709
- title: node.title,
710
- icon: node.icon,
711
- path: node.path,
712
- filePath: node.filePath,
713
- routes: node.subRoutes
714
- });
715
- else ungrouped.push(node);
716
- return {
717
- groups,
718
- ungrouped: finalizeTree(ungrouped),
719
- activeRoute,
720
- activePath: currentPath,
721
- config
722
- };
723
- }, [
724
- routes,
725
- config,
726
- pathname
727
- ]);
728
- }
729
-
730
849
  //#endregion
731
850
  //#region src/client/components/primitives/sidebar.tsx
732
851
  let sidebarScrollPos = 0;
@@ -820,13 +939,29 @@ function SidebarContent({ children, className }) {
820
939
  /**
821
940
  * Navigation Group
822
941
  */
823
- function SidebarGroup({ title, icon: Icon, children, className }) {
942
+ function SidebarGroup({ title, icon: Icon, children, className, collapsible = false, collapsed = false, active = false }) {
943
+ const [isOpen, setIsOpen] = useState(() => active || !collapsed);
944
+ const [prevActive, setPrevActive] = useState(active);
945
+ if (active !== prevActive) {
946
+ setPrevActive(active);
947
+ if (active) setIsOpen(true);
948
+ }
824
949
  return /* @__PURE__ */ jsxs("div", {
825
950
  className,
826
- children: [title && /* @__PURE__ */ jsxs("h4", {
951
+ children: [title && (collapsible ? /* @__PURE__ */ jsxs("button", {
952
+ onClick: () => setIsOpen(!isOpen),
953
+ className: "w-full text-left px-2 mb-2 flex items-center justify-between text-xs font-regular tracking-widest text-muted/50 hover:text-body transition-colors outline-none cursor-pointer group",
954
+ children: [/* @__PURE__ */ jsxs("span", {
955
+ className: "flex items-center gap-2",
956
+ children: [Icon && /* @__PURE__ */ jsx(Icon, { size: 12 }), title]
957
+ }), /* @__PURE__ */ jsx(ChevronRight, {
958
+ size: 12,
959
+ className: cn("transition-transform duration-200 text-muted/40 group-hover:text-body", isOpen && "rotate-90")
960
+ })]
961
+ }) : /* @__PURE__ */ jsxs("h4", {
827
962
  className: "px-2 mb-2 flex items-center gap-2 text-[11px] font-bold uppercase tracking-widest text-muted/50",
828
963
  children: [Icon && /* @__PURE__ */ jsx(Icon, { size: 12 }), title]
829
- }), /* @__PURE__ */ jsx("div", {
964
+ })), (!collapsible || isOpen) && /* @__PURE__ */ jsx("div", {
830
965
  className: "flex flex-col gap-0.5",
831
966
  children
832
967
  })]
@@ -917,23 +1052,65 @@ function SidebarItem({ route, activePath, activeRoute, className }) {
917
1052
  className
918
1053
  });
919
1054
  }
1055
+ const isRouteActive = (route, activePath, activeRoute) => {
1056
+ const normalizedPath = route.path.endsWith("/") ? route.path.slice(0, -1) : route.path;
1057
+ if ((activePath.endsWith("/") ? activePath.slice(0, -1) : activePath) === normalizedPath) return true;
1058
+ if (activeRoute?.filePath && route.filePath && activeRoute.filePath === route.filePath) return true;
1059
+ if (route.routes && route.routes.some((r) => isRouteActive(r, activePath, activeRoute))) return true;
1060
+ if (route.subRoutes && route.subRoutes.some((r) => isRouteActive(r, activePath, activeRoute))) return true;
1061
+ return false;
1062
+ };
920
1063
  function SidebarItems({ routes, className }) {
921
1064
  const { groups, ungrouped, activePath, activeRoute } = useSidebar(routes);
922
- return /* @__PURE__ */ jsxs("div", {
923
- className: cn("flex flex-col gap-6", className),
924
- children: [ungrouped.length > 0 && /* @__PURE__ */ jsx(SidebarGroup, { children: ungrouped.map((route) => /* @__PURE__ */ jsx(SidebarItem, {
925
- route,
926
- activePath,
927
- activeRoute
928
- }, route.path)) }), groups.map((group) => /* @__PURE__ */ jsx(SidebarGroup, {
929
- title: group.title,
930
- icon: getIcon(group.icon),
931
- children: group.routes.map((route) => /* @__PURE__ */ jsx(SidebarItem, {
1065
+ const mergedItems = [...ungrouped.map((route) => ({
1066
+ type: "link",
1067
+ position: route.sidebarPosition ?? 999,
1068
+ title: route.title,
1069
+ route
1070
+ })), ...groups.map((group) => ({
1071
+ type: "group",
1072
+ position: group.sidebarPosition ?? 999,
1073
+ title: group.title,
1074
+ group
1075
+ }))].sort((a, b) => {
1076
+ if (a.position !== b.position) return a.position - b.position;
1077
+ if (a.type !== b.type) return a.type === "link" ? -1 : 1;
1078
+ return a.title.localeCompare(b.title);
1079
+ });
1080
+ const renderedElements = [];
1081
+ let currentUngrouped = [];
1082
+ const pushUngrouped = () => {
1083
+ if (currentUngrouped.length > 0) {
1084
+ const routesToRender = [...currentUngrouped];
1085
+ renderedElements.push(/* @__PURE__ */ jsx(SidebarGroup, { children: routesToRender.map((route) => /* @__PURE__ */ jsx(SidebarItem, {
1086
+ route,
1087
+ activePath,
1088
+ activeRoute
1089
+ }, route.path)) }, `ungrouped-${routesToRender[0].path}`));
1090
+ currentUngrouped = [];
1091
+ }
1092
+ };
1093
+ for (const item of mergedItems) if (item.type === "link") currentUngrouped.push(item.route);
1094
+ else {
1095
+ pushUngrouped();
1096
+ const isGroupActive = item.group.routes.some((route) => isRouteActive(route, activePath, activeRoute));
1097
+ renderedElements.push(/* @__PURE__ */ jsx(SidebarGroup, {
1098
+ title: item.group.title,
1099
+ icon: getIcon(item.group.icon),
1100
+ collapsible: item.group.collapsible,
1101
+ collapsed: item.group.collapsed,
1102
+ active: isGroupActive,
1103
+ children: item.group.routes.map((route) => /* @__PURE__ */ jsx(SidebarItem, {
932
1104
  route,
933
1105
  activePath,
934
1106
  activeRoute
935
1107
  }, route.path))
936
- }, group.title))]
1108
+ }, item.group.title));
1109
+ }
1110
+ pushUngrouped();
1111
+ return /* @__PURE__ */ jsx("div", {
1112
+ className: cn("flex flex-col gap-6", className),
1113
+ children: renderedElements
937
1114
  });
938
1115
  }
939
1116
  /**
@@ -951,119 +1128,6 @@ const Sidebar = Object.assign(SidebarRoot, {
951
1128
  Items: SidebarItems
952
1129
  });
953
1130
 
954
- //#endregion
955
- //#region src/client/hooks/use-location.ts
956
- const useLocation$1 = () => {
957
- return useLocation();
958
- };
959
-
960
- //#endregion
961
- //#region src/client/hooks/use-search-highlight.ts
962
- /**
963
- * Hook to highlight search terms based on the 'hl' query parameter.
964
- */
965
- function useSearchHighlight(containerSelector = ".boltdocs-page") {
966
- const { search } = useLocation$1();
967
- const query = new URLSearchParams(search).get("hl");
968
- useEffect(() => {
969
- if (!query) {
970
- clearHighlights(containerSelector);
971
- return;
972
- }
973
- const container = document.querySelector(containerSelector);
974
- if (!container) return;
975
- let rafId;
976
- const observer = new MutationObserver((mutations) => {
977
- if (mutations.some((m) => {
978
- const addedNodes = Array.from(m.addedNodes);
979
- const removedNodes = Array.from(m.removedNodes);
980
- return addedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight"))) || removedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight")));
981
- })) run();
982
- });
983
- function run() {
984
- cancelAnimationFrame(rafId);
985
- rafId = requestAnimationFrame(() => {
986
- observer.disconnect();
987
- clearHighlights(containerSelector);
988
- const terms = query.split(/\s+/).map((t) => t.trim()).filter((t) => t.length >= 2);
989
- if (terms.length > 0) highlightTerms(container, terms);
990
- observer.observe(container, {
991
- childList: true,
992
- subtree: true
993
- });
994
- });
995
- }
996
- run();
997
- return () => {
998
- cancelAnimationFrame(rafId);
999
- observer.disconnect();
1000
- clearHighlights(containerSelector);
1001
- };
1002
- }, [
1003
- query,
1004
- search,
1005
- containerSelector
1006
- ]);
1007
- }
1008
- function clearHighlights(selector) {
1009
- document.querySelectorAll(`${selector} mark[data-search-highlight]`).forEach((mark) => {
1010
- try {
1011
- const parent = mark.parentNode;
1012
- if (parent && parent.contains(mark)) {
1013
- const text = mark.textContent || "";
1014
- parent.replaceChild(document.createTextNode(text), mark);
1015
- }
1016
- } catch (e) {}
1017
- });
1018
- }
1019
- function highlightTerms(container, terms) {
1020
- const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
1021
- const parent = node.parentElement;
1022
- if (parent && (parent.tagName === "SCRIPT" || parent.tagName === "STYLE" || parent.tagName === "MARK" || parent.closest("pre") || parent.closest("code"))) return NodeFilter.FILTER_REJECT;
1023
- return NodeFilter.FILTER_ACCEPT;
1024
- } });
1025
- const nodes = [];
1026
- let node;
1027
- while (node = walker.nextNode()) nodes.push(node);
1028
- const accentMap = {
1029
- a: "[aáàäâã]",
1030
- e: "[eéèëê]",
1031
- i: "[iíìïî]",
1032
- o: "[oóòöôõ]",
1033
- u: "[uúùüû]",
1034
- n: "[nñ]",
1035
- c: "[cç]"
1036
- };
1037
- const prepareRegex = (term) => {
1038
- let pattern = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1039
- pattern = pattern.split("").map((char) => {
1040
- return accentMap[char.toLowerCase()] || char;
1041
- }).join("");
1042
- return pattern;
1043
- };
1044
- const combinedPattern = terms.map(prepareRegex).join("|");
1045
- const regex = new RegExp(`(${combinedPattern})`, "gi");
1046
- const matchRegexes = terms.map((term) => {
1047
- const p = prepareRegex(term);
1048
- return new RegExp(`^${p}$`, "i");
1049
- });
1050
- nodes.forEach((textNode) => {
1051
- const text = textNode.textContent;
1052
- if (text && regex.test(text)) {
1053
- const fragment = document.createDocumentFragment();
1054
- text.split(regex).forEach((part) => {
1055
- if (matchRegexes.some((rx) => rx.test(part))) {
1056
- const mark = document.createElement("mark");
1057
- mark.textContent = part;
1058
- mark.setAttribute("data-search-highlight", "true");
1059
- fragment.appendChild(mark);
1060
- } else if (part) fragment.appendChild(document.createTextNode(part));
1061
- });
1062
- if (textNode.parentNode) textNode.parentNode.replaceChild(fragment, textNode);
1063
- }
1064
- });
1065
- }
1066
-
1067
1131
  //#endregion
1068
1132
  //#region src/client/components/ui-base/search-highlight.tsx
1069
1133
  /**
@@ -1154,4 +1218,4 @@ const DocsLayout = Object.assign(DocsLayoutRoot, {
1154
1218
  });
1155
1219
 
1156
1220
  //#endregion
1157
- export { useUI as A, useItems as C, ButtonGroup as D, Popover$1 as E, Breadcrumbs$1 as O, useActiveAnchors as S, Menu as T, OnThisPage as _, SidebarContent as a, ScrollProvider as b, SidebarItem as c, SidebarMobile as d, SidebarRoot as f, AnchorProvider as g, PageNav as h, Sidebar as i, UIProvider as k, SidebarItems as l, useSidebar as m, useSearchHighlight as n, SidebarGroup as o, SidebarSubGroup as p, useLocation$1 as r, SidebarHeader as s, DocsLayout as t, SidebarLink as u, OnThisPageItems as v, Tabs as w, useActiveAnchor as x, OnThisPageTree as y };
1221
+ export { useUI as A, useSidebar as C, ButtonGroup as D, Popover$1 as E, Breadcrumbs$1 as O, useLocation$1 as S, Menu as T, ScrollProvider as _, SidebarHeader as a, useItems as b, SidebarLink as c, SidebarSubGroup as d, PageNav as f, OnThisPageTree as g, OnThisPageItems as h, SidebarGroup as i, UIProvider as k, SidebarMobile as l, OnThisPage as m, Sidebar as n, SidebarItem as o, AnchorProvider as p, SidebarContent as r, SidebarItems as s, DocsLayout as t, SidebarRoot as u, useActiveAnchor as v, Tabs as w, useSearchHighlight as x, useActiveAnchors as y };