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
- const require_icons_dev = require('./icons-dev-C8sFxeBk.cjs');
6
+ const require_icons_dev = require('./icons-dev-BryEFkJu.cjs');
7
7
  let react = require("react");
8
8
  let react_router_dom = require("react-router-dom");
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
@@ -244,6 +244,235 @@ Tabs.Item = TabsItem;
244
244
  Tabs.Content = TabsContent;
245
245
  Tabs.Indicator = TabsIndicator;
246
246
 
247
+ //#endregion
248
+ //#region src/client/hooks/use-sidebar.ts
249
+ const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, " ");
250
+ const getCleanDirectoryMeta = (directoryMeta) => {
251
+ const meta = {};
252
+ if (!directoryMeta) return meta;
253
+ for (const [key, value] of Object.entries(directoryMeta)) {
254
+ const cleanKey = key.split("/").filter((part) => !part.startsWith("(") || !part.endsWith(")")).map((part) => part.replace(/^\d+-/, "")).join("/");
255
+ meta[cleanKey === "" ? "." : cleanKey] = value;
256
+ }
257
+ return meta;
258
+ };
259
+ const getOrCreateNode = (parts, rootMap, directoryMeta) => {
260
+ let currentMap = rootMap;
261
+ let parentPath = "";
262
+ let lastNode;
263
+ for (const segment of parts) {
264
+ const currentRelPath = parentPath ? `${parentPath}/${segment}` : segment;
265
+ if (!currentMap.has(segment)) {
266
+ const meta = directoryMeta[currentRelPath] || {};
267
+ const newNode = {
268
+ path: "#",
269
+ title: meta.title || capitalize(segment),
270
+ componentPath: "",
271
+ filePath: "",
272
+ icon: meta.icon,
273
+ groupPosition: typeof meta.order === "number" ? meta.order : 999,
274
+ collapsible: meta.collapsible,
275
+ collapsed: meta.collapsed,
276
+ hasCustomTitle: meta.title !== void 0,
277
+ subRoutes: [],
278
+ childrenMap: /* @__PURE__ */ new Map()
279
+ };
280
+ currentMap.set(segment, newNode);
281
+ }
282
+ lastNode = currentMap.get(segment);
283
+ currentMap = lastNode.childrenMap;
284
+ parentPath = currentRelPath;
285
+ }
286
+ return lastNode;
287
+ };
288
+ const getRoutePosition = (r) => r.sidebarPosition ?? r.order ?? 999;
289
+ const getNodePosition = (n) => n.sidebarPosition ?? n.groupPosition ?? 999;
290
+ const finalizeTree = (nodes) => {
291
+ return nodes.map((node) => {
292
+ if (node.childrenMap && node.childrenMap.size > 0) {
293
+ const childDirs = Array.from(node.childrenMap.values());
294
+ node.subRoutes = [...node.subRoutes || [], ...childDirs];
295
+ }
296
+ const { childrenMap, ...restNode } = node;
297
+ if (restNode.subRoutes && restNode.subRoutes.length > 0) restNode.subRoutes = finalizeTree(restNode.subRoutes);
298
+ return restNode;
299
+ }).sort((a, b) => {
300
+ const posA = getNodePosition(a);
301
+ const posB = getNodePosition(b);
302
+ return posA !== posB ? posA - posB : a.title.localeCompare(b.title);
303
+ });
304
+ };
305
+ function useSidebar(routes) {
306
+ const config = require_icons_dev.useConfig();
307
+ const { pathname } = (0, react_router_dom.useLocation)();
308
+ return (0, react.useMemo)(() => {
309
+ const currentPath = require_icons_dev.normalizePath(pathname);
310
+ const activeRoute = routes.find((r) => require_icons_dev.normalizePath(r.path) === currentPath);
311
+ const activeTabId = activeRoute?.tab?.toLowerCase();
312
+ 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));
313
+ const directoryMeta = getCleanDirectoryMeta(config.directoryMeta);
314
+ const rootNodesMap = /* @__PURE__ */ new Map();
315
+ const ungrouped = [];
316
+ for (const route of filteredRoutes) {
317
+ const parts = route.slugParts || [];
318
+ const isIndex = /^index\.mdx?$/.test(route.filePath.split("/").pop() || "");
319
+ if (parts.length === 0) {
320
+ if (route.filePath) ungrouped.push(route);
321
+ continue;
322
+ }
323
+ const containerNode = getOrCreateNode(parts, rootNodesMap, directoryMeta);
324
+ if (isIndex) Object.assign(containerNode, {
325
+ path: route.path,
326
+ title: containerNode.hasCustomTitle ? containerNode.title : route.title || containerNode.title,
327
+ icon: route.icon || containerNode.icon,
328
+ badge: route.badge,
329
+ sidebarPosition: route.sidebarPosition,
330
+ frontmatter: route.frontmatter,
331
+ filePath: route.filePath
332
+ });
333
+ else containerNode.subRoutes.push(route);
334
+ }
335
+ const finalizedTopNodes = finalizeTree(Array.from(rootNodesMap.values()));
336
+ const groups = [];
337
+ for (const node of finalizedTopNodes) if (node.subRoutes && node.subRoutes.length > 0) groups.push({
338
+ slug: node.title.toLowerCase().replace(/\s+/g, "-"),
339
+ title: node.title,
340
+ icon: node.icon,
341
+ path: node.path,
342
+ filePath: node.filePath,
343
+ routes: node.subRoutes,
344
+ sidebarPosition: node.sidebarPosition ?? node.groupPosition ?? 999,
345
+ collapsible: node.collapsible,
346
+ collapsed: node.collapsed
347
+ });
348
+ else ungrouped.push(node);
349
+ return {
350
+ groups,
351
+ ungrouped: finalizeTree(ungrouped),
352
+ activeRoute,
353
+ activePath: currentPath,
354
+ config
355
+ };
356
+ }, [
357
+ routes,
358
+ config,
359
+ pathname
360
+ ]);
361
+ }
362
+
363
+ //#endregion
364
+ //#region src/client/hooks/use-location.ts
365
+ const useLocation = () => {
366
+ return (0, react_router_dom.useLocation)();
367
+ };
368
+
369
+ //#endregion
370
+ //#region src/client/hooks/use-search-highlight.ts
371
+ /**
372
+ * Hook to highlight search terms based on the 'hl' query parameter.
373
+ */
374
+ function useSearchHighlight(containerSelector = ".boltdocs-page") {
375
+ const { search } = useLocation();
376
+ const query = new URLSearchParams(search).get("hl");
377
+ (0, react.useEffect)(() => {
378
+ if (!query) {
379
+ clearHighlights(containerSelector);
380
+ return;
381
+ }
382
+ const container = document.querySelector(containerSelector);
383
+ if (!container) return;
384
+ let rafId;
385
+ const observer = new MutationObserver((mutations) => {
386
+ if (mutations.some((m) => {
387
+ const addedNodes = Array.from(m.addedNodes);
388
+ const removedNodes = Array.from(m.removedNodes);
389
+ return addedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight"))) || removedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight")));
390
+ })) run();
391
+ });
392
+ function run() {
393
+ cancelAnimationFrame(rafId);
394
+ rafId = requestAnimationFrame(() => {
395
+ observer.disconnect();
396
+ clearHighlights(containerSelector);
397
+ const terms = query.split(/\s+/).map((t) => t.trim()).filter((t) => t.length >= 2);
398
+ if (terms.length > 0) highlightTerms(container, terms);
399
+ observer.observe(container, {
400
+ childList: true,
401
+ subtree: true
402
+ });
403
+ });
404
+ }
405
+ run();
406
+ return () => {
407
+ cancelAnimationFrame(rafId);
408
+ observer.disconnect();
409
+ clearHighlights(containerSelector);
410
+ };
411
+ }, [
412
+ query,
413
+ search,
414
+ containerSelector
415
+ ]);
416
+ }
417
+ function clearHighlights(selector) {
418
+ document.querySelectorAll(`${selector} mark[data-search-highlight]`).forEach((mark) => {
419
+ try {
420
+ const parent = mark.parentNode;
421
+ if (parent && parent.contains(mark)) {
422
+ const text = mark.textContent || "";
423
+ parent.replaceChild(document.createTextNode(text), mark);
424
+ }
425
+ } catch (e) {}
426
+ });
427
+ }
428
+ function highlightTerms(container, terms) {
429
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
430
+ const parent = node.parentElement;
431
+ if (parent && (parent.tagName === "SCRIPT" || parent.tagName === "STYLE" || parent.tagName === "MARK" || parent.closest("pre") || parent.closest("code"))) return NodeFilter.FILTER_REJECT;
432
+ return NodeFilter.FILTER_ACCEPT;
433
+ } });
434
+ const nodes = [];
435
+ let node;
436
+ while (node = walker.nextNode()) nodes.push(node);
437
+ const accentMap = {
438
+ a: "[aáàäâã]",
439
+ e: "[eéèëê]",
440
+ i: "[iíìïî]",
441
+ o: "[oóòöôõ]",
442
+ u: "[uúùüû]",
443
+ n: "[nñ]",
444
+ c: "[cç]"
445
+ };
446
+ const prepareRegex = (term) => {
447
+ let pattern = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
448
+ pattern = pattern.split("").map((char) => {
449
+ return accentMap[char.toLowerCase()] || char;
450
+ }).join("");
451
+ return pattern;
452
+ };
453
+ const combinedPattern = terms.map(prepareRegex).join("|");
454
+ const regex = new RegExp(`(${combinedPattern})`, "gi");
455
+ const matchRegexes = terms.map((term) => {
456
+ const p = prepareRegex(term);
457
+ return new RegExp(`^${p}$`, "i");
458
+ });
459
+ nodes.forEach((textNode) => {
460
+ const text = textNode.textContent;
461
+ if (text && regex.test(text)) {
462
+ const fragment = document.createDocumentFragment();
463
+ text.split(regex).forEach((part) => {
464
+ if (matchRegexes.some((rx) => rx.test(part))) {
465
+ const mark = document.createElement("mark");
466
+ mark.textContent = part;
467
+ mark.setAttribute("data-search-highlight", "true");
468
+ fragment.appendChild(mark);
469
+ } else if (part) fragment.appendChild(document.createTextNode(part));
470
+ });
471
+ if (textNode.parentNode) textNode.parentNode.replaceChild(fragment, textNode);
472
+ }
473
+ });
474
+ }
475
+
247
476
  //#endregion
248
477
  //#region src/client/components/primitives/helpers/observer.ts
249
478
  function getItemId(url) {
@@ -619,116 +848,6 @@ PageNav.Title = PageNavTitle;
619
848
  PageNav.Description = PageNavDescription;
620
849
  PageNav.Icon = PageNavIcon;
621
850
 
622
- //#endregion
623
- //#region src/client/hooks/use-sidebar.ts
624
- const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, " ");
625
- const getCleanDirectoryMeta = (directoryMeta) => {
626
- const meta = {};
627
- if (!directoryMeta) return meta;
628
- for (const [key, value] of Object.entries(directoryMeta)) {
629
- const cleanKey = key.split("/").filter((part) => !part.startsWith("(") || !part.endsWith(")")).map((part) => part.replace(/^\d+-/, "")).join("/");
630
- meta[cleanKey === "" ? "." : cleanKey] = value;
631
- }
632
- return meta;
633
- };
634
- const getOrCreateNode = (parts, rootMap, directoryMeta) => {
635
- let currentMap = rootMap;
636
- let parentPath = "";
637
- let lastNode;
638
- for (const segment of parts) {
639
- const currentRelPath = parentPath ? `${parentPath}/${segment}` : segment;
640
- if (!currentMap.has(segment)) {
641
- const meta = directoryMeta[currentRelPath] || {};
642
- const newNode = {
643
- path: "#",
644
- title: meta.title || capitalize(segment),
645
- componentPath: "",
646
- filePath: "",
647
- icon: meta.icon,
648
- groupPosition: typeof meta.order === "number" ? meta.order : 999,
649
- subRoutes: [],
650
- childrenMap: /* @__PURE__ */ new Map()
651
- };
652
- currentMap.set(segment, newNode);
653
- }
654
- lastNode = currentMap.get(segment);
655
- currentMap = lastNode.childrenMap;
656
- parentPath = currentRelPath;
657
- }
658
- return lastNode;
659
- };
660
- const getRoutePosition = (r) => r.sidebarPosition ?? r.order ?? 999;
661
- const getNodePosition = (n) => n.sidebarPosition ?? n.groupPosition ?? 999;
662
- const finalizeTree = (nodes) => {
663
- return nodes.map((node) => {
664
- if (node.childrenMap && node.childrenMap.size > 0) {
665
- const childDirs = Array.from(node.childrenMap.values());
666
- node.subRoutes = [...node.subRoutes || [], ...childDirs];
667
- }
668
- const { childrenMap, ...restNode } = node;
669
- if (restNode.subRoutes && restNode.subRoutes.length > 0) restNode.subRoutes = finalizeTree(restNode.subRoutes);
670
- return restNode;
671
- }).sort((a, b) => {
672
- const posA = getNodePosition(a);
673
- const posB = getNodePosition(b);
674
- return posA !== posB ? posA - posB : a.title.localeCompare(b.title);
675
- });
676
- };
677
- function useSidebar(routes) {
678
- const config = require_icons_dev.useConfig();
679
- const { pathname } = (0, react_router_dom.useLocation)();
680
- return (0, react.useMemo)(() => {
681
- const currentPath = require_icons_dev.normalizePath(pathname);
682
- const activeRoute = routes.find((r) => require_icons_dev.normalizePath(r.path) === currentPath);
683
- const activeTabId = activeRoute?.tab?.toLowerCase();
684
- 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));
685
- const directoryMeta = getCleanDirectoryMeta(config.directoryMeta);
686
- const rootNodesMap = /* @__PURE__ */ new Map();
687
- const ungrouped = [];
688
- for (const route of filteredRoutes) {
689
- const parts = route.slugParts || [];
690
- const isIndex = /^index\.mdx?$/.test(route.filePath.split("/").pop() || "");
691
- if (parts.length === 0) {
692
- if (route.filePath) ungrouped.push(route);
693
- continue;
694
- }
695
- const containerNode = getOrCreateNode(parts, rootNodesMap, directoryMeta);
696
- if (isIndex) Object.assign(containerNode, {
697
- path: route.path,
698
- title: route.title || containerNode.title,
699
- icon: route.icon || containerNode.icon,
700
- badge: route.badge,
701
- sidebarPosition: route.sidebarPosition,
702
- frontmatter: route.frontmatter,
703
- filePath: route.filePath
704
- });
705
- else containerNode.subRoutes.push(route);
706
- }
707
- const finalizedTopNodes = finalizeTree(Array.from(rootNodesMap.values()));
708
- const groups = [];
709
- for (const node of finalizedTopNodes) if (node.subRoutes && node.subRoutes.length > 0) groups.push({
710
- slug: node.title.toLowerCase().replace(/\s+/g, "-"),
711
- title: node.title,
712
- icon: node.icon,
713
- path: node.path,
714
- filePath: node.filePath,
715
- routes: node.subRoutes
716
- });
717
- else ungrouped.push(node);
718
- return {
719
- groups,
720
- ungrouped: finalizeTree(ungrouped),
721
- activeRoute,
722
- activePath: currentPath,
723
- config
724
- };
725
- }, [
726
- routes,
727
- config,
728
- pathname
729
- ]);
730
- }
731
-
732
851
  //#endregion
733
852
  //#region src/client/components/primitives/sidebar.tsx
734
853
  let sidebarScrollPos = 0;
@@ -822,13 +941,29 @@ function SidebarContent({ children, className }) {
822
941
  /**
823
942
  * Navigation Group
824
943
  */
825
- function SidebarGroup({ title, icon: Icon, children, className }) {
944
+ function SidebarGroup({ title, icon: Icon, children, className, collapsible = false, collapsed = false, active = false }) {
945
+ const [isOpen, setIsOpen] = (0, react.useState)(() => active || !collapsed);
946
+ const [prevActive, setPrevActive] = (0, react.useState)(active);
947
+ if (active !== prevActive) {
948
+ setPrevActive(active);
949
+ if (active) setIsOpen(true);
950
+ }
826
951
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
827
952
  className,
828
- children: [title && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h4", {
953
+ children: [title && (collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
954
+ onClick: () => setIsOpen(!isOpen),
955
+ 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",
956
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
957
+ className: "flex items-center gap-2",
958
+ children: [Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 12 }), title]
959
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_icons_dev.ChevronRight, {
960
+ size: 12,
961
+ className: require_icons_dev.cn("transition-transform duration-200 text-muted/40 group-hover:text-body", isOpen && "rotate-90")
962
+ })]
963
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h4", {
829
964
  className: "px-2 mb-2 flex items-center gap-2 text-[11px] font-bold uppercase tracking-widest text-muted/50",
830
965
  children: [Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 12 }), title]
831
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
966
+ })), (!collapsible || isOpen) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
832
967
  className: "flex flex-col gap-0.5",
833
968
  children
834
969
  })]
@@ -919,23 +1054,65 @@ function SidebarItem({ route, activePath, activeRoute, className }) {
919
1054
  className
920
1055
  });
921
1056
  }
1057
+ const isRouteActive = (route, activePath, activeRoute) => {
1058
+ const normalizedPath = route.path.endsWith("/") ? route.path.slice(0, -1) : route.path;
1059
+ if ((activePath.endsWith("/") ? activePath.slice(0, -1) : activePath) === normalizedPath) return true;
1060
+ if (activeRoute?.filePath && route.filePath && activeRoute.filePath === route.filePath) return true;
1061
+ if (route.routes && route.routes.some((r) => isRouteActive(r, activePath, activeRoute))) return true;
1062
+ if (route.subRoutes && route.subRoutes.some((r) => isRouteActive(r, activePath, activeRoute))) return true;
1063
+ return false;
1064
+ };
922
1065
  function SidebarItems({ routes, className }) {
923
1066
  const { groups, ungrouped, activePath, activeRoute } = useSidebar(routes);
924
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
925
- className: require_icons_dev.cn("flex flex-col gap-6", className),
926
- children: [ungrouped.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarGroup, { children: ungrouped.map((route) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarItem, {
927
- route,
928
- activePath,
929
- activeRoute
930
- }, route.path)) }), groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarGroup, {
931
- title: group.title,
932
- icon: getIcon(group.icon),
933
- children: group.routes.map((route) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarItem, {
1067
+ const mergedItems = [...ungrouped.map((route) => ({
1068
+ type: "link",
1069
+ position: route.sidebarPosition ?? 999,
1070
+ title: route.title,
1071
+ route
1072
+ })), ...groups.map((group) => ({
1073
+ type: "group",
1074
+ position: group.sidebarPosition ?? 999,
1075
+ title: group.title,
1076
+ group
1077
+ }))].sort((a, b) => {
1078
+ if (a.position !== b.position) return a.position - b.position;
1079
+ if (a.type !== b.type) return a.type === "link" ? -1 : 1;
1080
+ return a.title.localeCompare(b.title);
1081
+ });
1082
+ const renderedElements = [];
1083
+ let currentUngrouped = [];
1084
+ const pushUngrouped = () => {
1085
+ if (currentUngrouped.length > 0) {
1086
+ const routesToRender = [...currentUngrouped];
1087
+ renderedElements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarGroup, { children: routesToRender.map((route) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarItem, {
1088
+ route,
1089
+ activePath,
1090
+ activeRoute
1091
+ }, route.path)) }, `ungrouped-${routesToRender[0].path}`));
1092
+ currentUngrouped = [];
1093
+ }
1094
+ };
1095
+ for (const item of mergedItems) if (item.type === "link") currentUngrouped.push(item.route);
1096
+ else {
1097
+ pushUngrouped();
1098
+ const isGroupActive = item.group.routes.some((route) => isRouteActive(route, activePath, activeRoute));
1099
+ renderedElements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarGroup, {
1100
+ title: item.group.title,
1101
+ icon: getIcon(item.group.icon),
1102
+ collapsible: item.group.collapsible,
1103
+ collapsed: item.group.collapsed,
1104
+ active: isGroupActive,
1105
+ children: item.group.routes.map((route) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarItem, {
934
1106
  route,
935
1107
  activePath,
936
1108
  activeRoute
937
1109
  }, route.path))
938
- }, group.title))]
1110
+ }, item.group.title));
1111
+ }
1112
+ pushUngrouped();
1113
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1114
+ className: require_icons_dev.cn("flex flex-col gap-6", className),
1115
+ children: renderedElements
939
1116
  });
940
1117
  }
941
1118
  /**
@@ -953,119 +1130,6 @@ const Sidebar = Object.assign(SidebarRoot, {
953
1130
  Items: SidebarItems
954
1131
  });
955
1132
 
956
- //#endregion
957
- //#region src/client/hooks/use-location.ts
958
- const useLocation = () => {
959
- return (0, react_router_dom.useLocation)();
960
- };
961
-
962
- //#endregion
963
- //#region src/client/hooks/use-search-highlight.ts
964
- /**
965
- * Hook to highlight search terms based on the 'hl' query parameter.
966
- */
967
- function useSearchHighlight(containerSelector = ".boltdocs-page") {
968
- const { search } = useLocation();
969
- const query = new URLSearchParams(search).get("hl");
970
- (0, react.useEffect)(() => {
971
- if (!query) {
972
- clearHighlights(containerSelector);
973
- return;
974
- }
975
- const container = document.querySelector(containerSelector);
976
- if (!container) return;
977
- let rafId;
978
- const observer = new MutationObserver((mutations) => {
979
- if (mutations.some((m) => {
980
- const addedNodes = Array.from(m.addedNodes);
981
- const removedNodes = Array.from(m.removedNodes);
982
- return addedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight"))) || removedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight")));
983
- })) run();
984
- });
985
- function run() {
986
- cancelAnimationFrame(rafId);
987
- rafId = requestAnimationFrame(() => {
988
- observer.disconnect();
989
- clearHighlights(containerSelector);
990
- const terms = query.split(/\s+/).map((t) => t.trim()).filter((t) => t.length >= 2);
991
- if (terms.length > 0) highlightTerms(container, terms);
992
- observer.observe(container, {
993
- childList: true,
994
- subtree: true
995
- });
996
- });
997
- }
998
- run();
999
- return () => {
1000
- cancelAnimationFrame(rafId);
1001
- observer.disconnect();
1002
- clearHighlights(containerSelector);
1003
- };
1004
- }, [
1005
- query,
1006
- search,
1007
- containerSelector
1008
- ]);
1009
- }
1010
- function clearHighlights(selector) {
1011
- document.querySelectorAll(`${selector} mark[data-search-highlight]`).forEach((mark) => {
1012
- try {
1013
- const parent = mark.parentNode;
1014
- if (parent && parent.contains(mark)) {
1015
- const text = mark.textContent || "";
1016
- parent.replaceChild(document.createTextNode(text), mark);
1017
- }
1018
- } catch (e) {}
1019
- });
1020
- }
1021
- function highlightTerms(container, terms) {
1022
- const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
1023
- const parent = node.parentElement;
1024
- if (parent && (parent.tagName === "SCRIPT" || parent.tagName === "STYLE" || parent.tagName === "MARK" || parent.closest("pre") || parent.closest("code"))) return NodeFilter.FILTER_REJECT;
1025
- return NodeFilter.FILTER_ACCEPT;
1026
- } });
1027
- const nodes = [];
1028
- let node;
1029
- while (node = walker.nextNode()) nodes.push(node);
1030
- const accentMap = {
1031
- a: "[aáàäâã]",
1032
- e: "[eéèëê]",
1033
- i: "[iíìïî]",
1034
- o: "[oóòöôõ]",
1035
- u: "[uúùüû]",
1036
- n: "[nñ]",
1037
- c: "[cç]"
1038
- };
1039
- const prepareRegex = (term) => {
1040
- let pattern = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1041
- pattern = pattern.split("").map((char) => {
1042
- return accentMap[char.toLowerCase()] || char;
1043
- }).join("");
1044
- return pattern;
1045
- };
1046
- const combinedPattern = terms.map(prepareRegex).join("|");
1047
- const regex = new RegExp(`(${combinedPattern})`, "gi");
1048
- const matchRegexes = terms.map((term) => {
1049
- const p = prepareRegex(term);
1050
- return new RegExp(`^${p}$`, "i");
1051
- });
1052
- nodes.forEach((textNode) => {
1053
- const text = textNode.textContent;
1054
- if (text && regex.test(text)) {
1055
- const fragment = document.createDocumentFragment();
1056
- text.split(regex).forEach((part) => {
1057
- if (matchRegexes.some((rx) => rx.test(part))) {
1058
- const mark = document.createElement("mark");
1059
- mark.textContent = part;
1060
- mark.setAttribute("data-search-highlight", "true");
1061
- fragment.appendChild(mark);
1062
- } else if (part) fragment.appendChild(document.createTextNode(part));
1063
- });
1064
- if (textNode.parentNode) textNode.parentNode.replaceChild(fragment, textNode);
1065
- }
1066
- });
1067
- }
1068
-
1069
1133
  //#endregion
1070
1134
  //#region src/client/components/ui-base/search-highlight.tsx
1071
1135
  /**