boltdocs 2.8.3 → 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-MynZD_Ox.d.cts → banner-C_xkGxzI.d.cts} +1 -1
  3. package/dist/{banner-3N4Jd_L9.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 +693 -488
  8. package/dist/client/index.d.cts +74 -8
  9. package/dist/client/index.d.ts +74 -8
  10. package/dist/client/index.js +692 -489
  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-CwCq42Zt.cjs → docs-layout-3TpAt6MB.cjs} +302 -248
  20. package/dist/{docs-layout-Dn6S5g59.js → docs-layout-6FQ8__ok.js} +303 -249
  21. package/dist/doctor-BY1tSmQd.mjs +34 -0
  22. package/dist/{doctor-DyNUVe96.cjs → doctor-Bknon8f5.cjs} +1 -1
  23. package/dist/doctor-DcFLi4t1.cjs +34 -0
  24. package/dist/{doctor-aN_leTbh.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-DvJ-hh9x.cjs → icons-dev-BryEFkJu.cjs} +96 -7
  31. package/dist/{icons-dev-Oju24Wjp.js → icons-dev-kJyGWNeg.js} +79 -8
  32. package/dist/{image-Ch4-GxdO.cjs → image-B9YA_oQ_.cjs} +24 -10
  33. package/dist/{image-Do8V9PCW.js → image-CjYu9Eml.js} +24 -10
  34. package/dist/{mdx-5oeCOFhH.js → mdx-Cy_c2zio.js} +116 -33
  35. package/dist/{mdx-BGM7LjW5.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-CHxJV4Jy.mjs → package-BK_82QLX.mjs} +1 -1
  48. package/dist/{package-B_SoaGeu.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-D0PVghVt.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-BXVoecTx.cjs → search-dialog-BhXg05Ri.cjs} +1 -1
  57. package/dist/{search-dialog-BYhOov4S.cjs → search-dialog-BsAiQgnx.cjs} +2 -2
  58. package/dist/{search-dialog-C09riYmx.js → search-dialog-CaOO1Q4i.js} +2 -2
  59. package/dist/{search-dialog-CUeAfy-8.cjs → search-dialog-CzwvKoLW.cjs} +1 -1
  60. package/dist/{search-dialog-DHc_8FFX.js → search-dialog-DRNJsxOv.js} +1 -1
  61. package/dist/{search-dialog-D8gLkhUV.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-Dlkgbxs6.d.cts → sidebar-BtJyWHy4.d.cts} +43 -4
  67. package/dist/{sidebar-DNq4_ZAa.d.ts → sidebar-C4fuRYYS.d.ts} +43 -4
  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 +7 -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-BArviV8X.cjs +0 -28
  78. package/dist/doctor-CgLA7_Uv.mjs +0 -28
  79. package/dist/node-MsTvOgJN.mjs +0 -159
  80. package/dist/node-h9HmhAFB.cjs +0 -159
  81. package/dist/routes-BW6Dif_K.mjs +0 -6
  82. package/dist/routes-DJNJ-rTt.cjs +0 -6
  83. package/dist/routes-DiYC4nD2.cjs +0 -6
  84. package/dist/routes-ERSGuFQK.mjs +0 -6
  85. package/dist/worker-pool-CtqklOXq.cjs +0 -6
  86. package/dist/worker-pool-k0DY6k8T.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-DvJ-hh9x.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,126 +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
- function useSidebar(routes) {
625
- const config = require_icons_dev.useConfig();
626
- const currentPath = require_icons_dev.normalizePath((0, react_router_dom.useLocation)().pathname);
627
- return (0, react.useMemo)(() => {
628
- const activeRoute = routes.find((r) => require_icons_dev.normalizePath(r.path) === currentPath);
629
- const activeTabId = activeRoute?.tab?.toLowerCase();
630
- const noCollection = routes.filter((r) => !r.collection);
631
- const filteredRoutes = activeTabId ? noCollection.filter((r) => !r.tab || r.tab.toLowerCase() === activeTabId) : noCollection;
632
- const directoryMeta = {};
633
- if (config.directoryMeta) for (const [key, value] of Object.entries(config.directoryMeta)) {
634
- const cleanKey = key.split("/").filter((part) => !part.startsWith("(") || !part.endsWith(")")).map((part) => part.replace(/^\d+-/, "")).join("/");
635
- directoryMeta[cleanKey === "" ? "." : cleanKey] = value;
636
- }
637
- const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, " ");
638
- const rootNodesMap = /* @__PURE__ */ new Map();
639
- const ungrouped = [];
640
- const getOrCreateNode = (parts, rootStore) => {
641
- let currentMap = rootStore;
642
- let parentPath = "";
643
- let lastNode = null;
644
- for (let i = 0; i < parts.length; i++) {
645
- const segment = parts[i];
646
- const currentRelPath = parentPath ? `${parentPath}/${segment}` : segment;
647
- if (!currentMap.has(segment)) {
648
- const meta = directoryMeta[currentRelPath] || {};
649
- const newNode = {
650
- path: "#",
651
- title: meta.title || capitalize(segment),
652
- componentPath: "",
653
- filePath: "",
654
- icon: meta.icon,
655
- groupPosition: typeof meta.order === "number" ? meta.order : 999,
656
- subRoutes: []
657
- };
658
- currentMap.set(segment, newNode);
659
- }
660
- lastNode = currentMap.get(segment);
661
- if (!lastNode._subMap) lastNode._subMap = /* @__PURE__ */ new Map();
662
- currentMap = lastNode._subMap;
663
- parentPath = currentRelPath;
664
- }
665
- return lastNode;
666
- };
667
- const sortedRoutes = [...filteredRoutes].sort((a, b) => {
668
- return (a.sidebarPosition ?? a.order ?? 999) - (b.sidebarPosition ?? b.order ?? 999);
669
- });
670
- for (const route of sortedRoutes) {
671
- if (route.sidebarHidden) continue;
672
- const parts = route.slugParts || [];
673
- const fileName = route.filePath.split("/").pop() || "";
674
- const isIndex = /^index\.mdx?$/.test(fileName);
675
- if (parts.length === 0) {
676
- if (route.filePath) ungrouped.push(route);
677
- continue;
678
- }
679
- if (isIndex) {
680
- const containerNode = getOrCreateNode(parts, rootNodesMap);
681
- if (containerNode) {
682
- containerNode.path = route.path;
683
- containerNode.title = route.title || containerNode.title;
684
- containerNode.icon = route.icon || containerNode.icon;
685
- containerNode.badge = route.badge;
686
- containerNode.sidebarPosition = route.sidebarPosition;
687
- containerNode.frontmatter = route.frontmatter;
688
- }
689
- } else {
690
- const parentNode = getOrCreateNode(parts, rootNodesMap);
691
- if (parentNode) parentNode.subRoutes.push(route);
692
- }
693
- }
694
- const finalizeTree = (nodes, currentPathPrefix = "") => {
695
- nodes.forEach((node) => {
696
- if (node._subMap) {
697
- const childDirs = Array.from(node._subMap.values());
698
- node.subRoutes = [...node.subRoutes || [], ...childDirs];
699
- delete node._subMap;
700
- }
701
- if (node.subRoutes && node.subRoutes.length > 0) node.subRoutes = finalizeTree(node.subRoutes);
702
- });
703
- return nodes.sort((a, b) => {
704
- const posA = a.sidebarPosition ?? a.groupPosition ?? 999;
705
- const posB = b.sidebarPosition ?? b.groupPosition ?? 999;
706
- if (posA !== posB) return posA - posB;
707
- return a.title.localeCompare(b.title);
708
- });
709
- };
710
- const finalizedTopNodes = finalizeTree(Array.from(rootNodesMap.values()));
711
- finalizedTopNodes.map((node) => {
712
- return {
713
- slug: node.title.toLowerCase().replace(/\s+/g, "-"),
714
- title: node.title,
715
- icon: node.icon,
716
- routes: [node]
717
- };
718
- });
719
- return {
720
- groups: finalizedTopNodes.map((node) => {
721
- if (node.subRoutes && node.subRoutes.length > 0) return {
722
- slug: node.title.toLowerCase().replace(/\s+/g, "-"),
723
- title: node.title,
724
- icon: node.icon,
725
- routes: node.subRoutes
726
- };
727
- ungrouped.push(node);
728
- return null;
729
- }).filter(Boolean),
730
- ungrouped: finalizeTree(ungrouped),
731
- activeRoute,
732
- activePath: currentPath,
733
- config
734
- };
735
- }, [
736
- routes,
737
- config,
738
- currentPath
739
- ]);
740
- }
741
-
742
851
  //#endregion
743
852
  //#region src/client/components/primitives/sidebar.tsx
744
853
  let sidebarScrollPos = 0;
@@ -832,13 +941,29 @@ function SidebarContent({ children, className }) {
832
941
  /**
833
942
  * Navigation Group
834
943
  */
835
- 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
+ }
836
951
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
837
952
  className,
838
- 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", {
839
964
  className: "px-2 mb-2 flex items-center gap-2 text-[11px] font-bold uppercase tracking-widest text-muted/50",
840
965
  children: [Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 12 }), title]
841
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
966
+ })), (!collapsible || isOpen) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
842
967
  className: "flex flex-col gap-0.5",
843
968
  children
844
969
  })]
@@ -929,23 +1054,65 @@ function SidebarItem({ route, activePath, activeRoute, className }) {
929
1054
  className
930
1055
  });
931
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
+ };
932
1065
  function SidebarItems({ routes, className }) {
933
1066
  const { groups, ungrouped, activePath, activeRoute } = useSidebar(routes);
934
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
935
- className: require_icons_dev.cn("flex flex-col gap-6", className),
936
- children: [ungrouped.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarGroup, { children: ungrouped.map((route) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarItem, {
937
- route,
938
- activePath,
939
- activeRoute
940
- }, route.path)) }), groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarGroup, {
941
- title: group.title,
942
- icon: getIcon(group.icon),
943
- 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, {
944
1106
  route,
945
1107
  activePath,
946
1108
  activeRoute
947
1109
  }, route.path))
948
- }, 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
949
1116
  });
950
1117
  }
951
1118
  /**
@@ -963,119 +1130,6 @@ const Sidebar = Object.assign(SidebarRoot, {
963
1130
  Items: SidebarItems
964
1131
  });
965
1132
 
966
- //#endregion
967
- //#region src/client/hooks/use-location.ts
968
- const useLocation = () => {
969
- return (0, react_router_dom.useLocation)();
970
- };
971
-
972
- //#endregion
973
- //#region src/client/hooks/use-search-highlight.ts
974
- /**
975
- * Hook to highlight search terms based on the 'hl' query parameter.
976
- */
977
- function useSearchHighlight(containerSelector = ".boltdocs-page") {
978
- const { search } = useLocation();
979
- const query = new URLSearchParams(search).get("hl");
980
- (0, react.useEffect)(() => {
981
- if (!query) {
982
- clearHighlights(containerSelector);
983
- return;
984
- }
985
- const container = document.querySelector(containerSelector);
986
- if (!container) return;
987
- let rafId;
988
- const observer = new MutationObserver((mutations) => {
989
- if (mutations.some((m) => {
990
- const addedNodes = Array.from(m.addedNodes);
991
- const removedNodes = Array.from(m.removedNodes);
992
- return addedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight"))) || removedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight")));
993
- })) run();
994
- });
995
- function run() {
996
- cancelAnimationFrame(rafId);
997
- rafId = requestAnimationFrame(() => {
998
- observer.disconnect();
999
- clearHighlights(containerSelector);
1000
- const terms = query.split(/\s+/).map((t) => t.trim()).filter((t) => t.length >= 2);
1001
- if (terms.length > 0) highlightTerms(container, terms);
1002
- observer.observe(container, {
1003
- childList: true,
1004
- subtree: true
1005
- });
1006
- });
1007
- }
1008
- run();
1009
- return () => {
1010
- cancelAnimationFrame(rafId);
1011
- observer.disconnect();
1012
- clearHighlights(containerSelector);
1013
- };
1014
- }, [
1015
- query,
1016
- search,
1017
- containerSelector
1018
- ]);
1019
- }
1020
- function clearHighlights(selector) {
1021
- document.querySelectorAll(`${selector} mark[data-search-highlight]`).forEach((mark) => {
1022
- try {
1023
- const parent = mark.parentNode;
1024
- if (parent && parent.contains(mark)) {
1025
- const text = mark.textContent || "";
1026
- parent.replaceChild(document.createTextNode(text), mark);
1027
- }
1028
- } catch (e) {}
1029
- });
1030
- }
1031
- function highlightTerms(container, terms) {
1032
- const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
1033
- const parent = node.parentElement;
1034
- if (parent && (parent.tagName === "SCRIPT" || parent.tagName === "STYLE" || parent.tagName === "MARK" || parent.closest("pre") || parent.closest("code"))) return NodeFilter.FILTER_REJECT;
1035
- return NodeFilter.FILTER_ACCEPT;
1036
- } });
1037
- const nodes = [];
1038
- let node;
1039
- while (node = walker.nextNode()) nodes.push(node);
1040
- const accentMap = {
1041
- a: "[aáàäâã]",
1042
- e: "[eéèëê]",
1043
- i: "[iíìïî]",
1044
- o: "[oóòöôõ]",
1045
- u: "[uúùüû]",
1046
- n: "[nñ]",
1047
- c: "[cç]"
1048
- };
1049
- const prepareRegex = (term) => {
1050
- let pattern = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1051
- pattern = pattern.split("").map((char) => {
1052
- return accentMap[char.toLowerCase()] || char;
1053
- }).join("");
1054
- return pattern;
1055
- };
1056
- const combinedPattern = terms.map(prepareRegex).join("|");
1057
- const regex = new RegExp(`(${combinedPattern})`, "gi");
1058
- const matchRegexes = terms.map((term) => {
1059
- const p = prepareRegex(term);
1060
- return new RegExp(`^${p}$`, "i");
1061
- });
1062
- nodes.forEach((textNode) => {
1063
- const text = textNode.textContent;
1064
- if (text && regex.test(text)) {
1065
- const fragment = document.createDocumentFragment();
1066
- text.split(regex).forEach((part) => {
1067
- if (matchRegexes.some((rx) => rx.test(part))) {
1068
- const mark = document.createElement("mark");
1069
- mark.textContent = part;
1070
- mark.setAttribute("data-search-highlight", "true");
1071
- fragment.appendChild(mark);
1072
- } else if (part) fragment.appendChild(document.createTextNode(part));
1073
- });
1074
- if (textNode.parentNode) textNode.parentNode.replaceChild(fragment, textNode);
1075
- }
1076
- });
1077
- }
1078
-
1079
1133
  //#endregion
1080
1134
  //#region src/client/components/ui-base/search-highlight.tsx
1081
1135
  /**