@marktiderman/genesis-ui 2.12.0 → 2.12.2

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 (125) hide show
  1. package/dist/{AppShell-DpQdcLE-.d.ts → AppShell-HCXpi6nv.d.ts} +3 -2
  2. package/dist/chunk-424PT5DM.js +21 -0
  3. package/dist/chunk-424PT5DM.js.map +1 -0
  4. package/dist/chunk-J45FHL7D.js +46 -0
  5. package/dist/chunk-J45FHL7D.js.map +1 -0
  6. package/dist/{chunk-SZMXNC5Q.js → chunk-MLZEKXV2.js} +3 -3
  7. package/dist/{chunk-SZMXNC5Q.js.map → chunk-MLZEKXV2.js.map} +1 -1
  8. package/dist/{chunk-XGG5RUDE.js → chunk-PMCUHSZS.js} +3 -3
  9. package/dist/{chunk-XGG5RUDE.js.map → chunk-PMCUHSZS.js.map} +1 -1
  10. package/dist/{chunk-3TWCZYZY.js → chunk-STZVZUYO.js} +3 -3
  11. package/dist/{chunk-3TWCZYZY.js.map → chunk-STZVZUYO.js.map} +1 -1
  12. package/dist/chunk-WALJMVEF.js +10 -0
  13. package/dist/chunk-WALJMVEF.js.map +1 -0
  14. package/dist/components/data/index.d.ts +7 -6
  15. package/dist/components/data/index.js +14 -13
  16. package/dist/components/data/index.js.map +1 -1
  17. package/dist/components/layout/dashboard-page.js +1 -0
  18. package/dist/components/layout/detail-page.js +1 -0
  19. package/dist/components/layout/form-page.js +1 -0
  20. package/dist/components/layout/index.d.ts +3 -2
  21. package/dist/components/layout/index.js +3 -2
  22. package/dist/components/layout/settings-page.js +1 -0
  23. package/dist/components/patterns/empty-state.js +1 -0
  24. package/dist/components/patterns/form-field.js +1 -0
  25. package/dist/components/patterns/index.js +1 -0
  26. package/dist/components/patterns/page-loading.js +1 -0
  27. package/dist/components/patterns/settings-row.js +1 -0
  28. package/dist/components/patterns/sortable-list.js +1 -0
  29. package/dist/components/patterns/status-badge.js +1 -0
  30. package/dist/components/patterns/toggle-row.js +1 -0
  31. package/dist/components/patterns/user-avatar.js +1 -0
  32. package/dist/components/rich-text/rich-text-editor.js +1 -0
  33. package/dist/components/rich-text/rich-text-editor.js.map +1 -1
  34. package/dist/components/rich-text/rich-text.js +1 -0
  35. package/dist/components/sheet-grid/sheet-grid.js +1 -0
  36. package/dist/components/sheet-grid/sheet-grid.js.map +1 -1
  37. package/dist/components/ui/accessible-icon.js +1 -0
  38. package/dist/components/ui/accordion.js +1 -0
  39. package/dist/components/ui/alert-dialog.js +1 -0
  40. package/dist/components/ui/alert.js +1 -0
  41. package/dist/components/ui/aspect-ratio.js +1 -0
  42. package/dist/components/ui/avatar.js +1 -0
  43. package/dist/components/ui/badge.js +1 -0
  44. package/dist/components/ui/breadcrumb.js +1 -0
  45. package/dist/components/ui/button-group.js +1 -0
  46. package/dist/components/ui/button.js +1 -0
  47. package/dist/components/ui/calendar.js +1 -0
  48. package/dist/components/ui/card.js +1 -0
  49. package/dist/components/ui/carousel.js +1 -0
  50. package/dist/components/ui/chart.js +1 -0
  51. package/dist/components/ui/checkbox.js +1 -0
  52. package/dist/components/ui/collapsible.js +1 -0
  53. package/dist/components/ui/command-palette.js +1 -0
  54. package/dist/components/ui/command.js +1 -0
  55. package/dist/components/ui/context-menu.js +1 -0
  56. package/dist/components/ui/date-picker.js +1 -0
  57. package/dist/components/ui/dialog.js +1 -0
  58. package/dist/components/ui/drawer.js +1 -0
  59. package/dist/components/ui/dropdown-menu.js +1 -0
  60. package/dist/components/ui/hover-card.js +1 -0
  61. package/dist/components/ui/input-group.js +1 -0
  62. package/dist/components/ui/input-otp.js +1 -0
  63. package/dist/components/ui/input.js +1 -0
  64. package/dist/components/ui/kbd.js +1 -0
  65. package/dist/components/ui/label.js +1 -0
  66. package/dist/components/ui/menubar.js +1 -0
  67. package/dist/components/ui/navigation-menu.js +1 -0
  68. package/dist/components/ui/pagination.js +1 -0
  69. package/dist/components/ui/password-toggle-field.js +1 -0
  70. package/dist/components/ui/popover.js +1 -0
  71. package/dist/components/ui/progress.js +1 -0
  72. package/dist/components/ui/radio-group.js +1 -0
  73. package/dist/components/ui/resizable.js +1 -0
  74. package/dist/components/ui/scroll-area.js +1 -0
  75. package/dist/components/ui/select.js +1 -0
  76. package/dist/components/ui/separator.js +1 -0
  77. package/dist/components/ui/sheet.js +1 -0
  78. package/dist/components/ui/skeleton.js +1 -0
  79. package/dist/components/ui/slider.js +1 -0
  80. package/dist/components/ui/sonner.js +1 -0
  81. package/dist/components/ui/spinner.js +1 -0
  82. package/dist/components/ui/switch.js +1 -0
  83. package/dist/components/ui/table.js +1 -0
  84. package/dist/components/ui/tabs.js +1 -0
  85. package/dist/components/ui/textarea.js +1 -0
  86. package/dist/components/ui/toggle-group.js +1 -0
  87. package/dist/components/ui/toggle.js +1 -0
  88. package/dist/components/ui/toolbar.js +1 -0
  89. package/dist/components/ui/tooltip.js +1 -0
  90. package/dist/components/ui/typography.js +1 -0
  91. package/dist/components/ui/visually-hidden.js +1 -0
  92. package/dist/hooks/index.d.ts +3 -3
  93. package/dist/hooks/index.js +4 -3
  94. package/dist/hooks/use-data-filters.d.ts +1 -1
  95. package/dist/hooks/use-data-filters.js +3 -2
  96. package/dist/hooks/use-keyboard-navigation.js +1 -0
  97. package/dist/hooks/use-mobile.js +1 -0
  98. package/dist/hooks/use-prefetch.js +1 -0
  99. package/dist/hooks/use-resource-page.js +1 -0
  100. package/dist/hooks/use-resource-url-state.d.ts +1 -1
  101. package/dist/hooks/use-resource-url-state.js +1 -0
  102. package/dist/hooks/use-saved-views.js +1 -0
  103. package/dist/hooks/use-view-preference.js +1 -0
  104. package/dist/hooks/use-view-settings.js +1 -0
  105. package/dist/index.d.ts +3 -2
  106. package/dist/index.js +360 -2
  107. package/dist/index.js.map +1 -1
  108. package/dist/navigation-vmFJ6r1m.d.ts +90 -0
  109. package/dist/router/react-router.d.ts +1 -1
  110. package/dist/router/react-router.js +1 -0
  111. package/dist/router/react-router.js.map +1 -1
  112. package/dist/router/tanstack-router.d.ts +1 -1
  113. package/dist/router/tanstack-router.js +2 -1
  114. package/dist/router/tanstack-router.js.map +1 -1
  115. package/dist/status-colors.js +2 -0
  116. package/dist/status-colors.js.map +1 -1
  117. package/dist/{use-resource-table-DrTkD5O1.d.ts → use-resource-table-easmRDQI.d.ts} +2 -2
  118. package/dist/utils.js +1 -0
  119. package/package.json +4 -4
  120. package/dist/chunk-7S7LKQBD.js +0 -12
  121. package/dist/chunk-7S7LKQBD.js.map +0 -1
  122. package/dist/chunk-KC43CNMA.js +0 -141
  123. package/dist/chunk-KC43CNMA.js.map +0 -1
  124. package/dist/navigation-By1DrzaM.d.ts +0 -190
  125. package/dist/slots-tR0wf89f.d.ts +0 -60
@@ -0,0 +1,90 @@
1
+ import { ComponentType, ReactNode, AnchorHTMLAttributes } from 'react';
2
+
3
+ /**
4
+ * Router-agnostic navigation primitives.
5
+ *
6
+ * genesis-ui does NOT depend on any router. Components that need to render a
7
+ * link or navigate imperatively accept an injectable abstraction defined here,
8
+ * and default to plain browser behavior (`<a>` / `window.location`). Consumers
9
+ * on a router inject an adapter that maps these props to their own `<Link>` /
10
+ * router. Ready-made ones ship on their own subpaths, each with its router as
11
+ * an optional peer: `@marktiderman/genesis-ui/router/react-router` and
12
+ * `@marktiderman/genesis-ui/router/tanstack-router`. Next.js passes
13
+ * `next/link` directly.
14
+ */
15
+
16
+ /**
17
+ * Props passed to an injectable link component. The contract is a plain anchor
18
+ * (`href`), so the default works with NO router. A React Router consumer maps
19
+ * `href` → `to`; a Next.js consumer passes `next/link` directly (it already
20
+ * accepts `href`, `className`, `children`).
21
+ *
22
+ * @stability Beta
23
+ */
24
+ interface GenesisLinkProps {
25
+ /** Destination path or URL. */
26
+ href: string;
27
+ className?: string;
28
+ onClick?: () => void;
29
+ children?: ReactNode;
30
+ /** Set to "page" when the link points at the active route. */
31
+ "aria-current"?: AnchorHTMLAttributes<HTMLAnchorElement>["aria-current"];
32
+ /** Accessible name — useful when the visible label is hidden (icon rail). */
33
+ "aria-label"?: string;
34
+ /** Native tooltip / title attribute. */
35
+ title?: string;
36
+ /**
37
+ * Stable test/automation selector. {@link DefaultLink} renders it as
38
+ * `data-testid`; injected router adapters (`next/link`, RR `Link`) forward it
39
+ * to the underlying `<a>` too.
40
+ */
41
+ testID?: string;
42
+ }
43
+ /**
44
+ * A component that renders a navigable link from {@link GenesisLinkProps}.
45
+ *
46
+ * @stability Beta
47
+ */
48
+ type LinkComponent = ComponentType<GenesisLinkProps>;
49
+ /**
50
+ * Default link — a plain `<a>`. No router required.
51
+ *
52
+ * @stability Beta
53
+ */
54
+ declare const DefaultLink: LinkComponent;
55
+
56
+ /**
57
+ * Injectable imperative navigation callback.
58
+ *
59
+ * @stability Beta
60
+ */
61
+ type NavigateFn = (path: string) => void;
62
+ /**
63
+ * Default navigate — a full-page navigation. No router required.
64
+ *
65
+ * @stability Beta
66
+ */
67
+ declare const defaultNavigate: NavigateFn;
68
+ /**
69
+ * Minimal, router-agnostic replacement for React Router's `useSearchParams`
70
+ * return shape. Consumers may inject their router's implementation for full
71
+ * SPA integration; otherwise the browser History API is used.
72
+ *
73
+ * @stability Beta
74
+ */
75
+ interface SearchParamsAdapter {
76
+ searchParams: URLSearchParams;
77
+ setSearchParams: (next: URLSearchParams, options?: {
78
+ replace?: boolean;
79
+ }) => void;
80
+ }
81
+ /**
82
+ * Browser-native {@link SearchParamsAdapter} built on the History API. Reads
83
+ * the current query string and writes updates via `pushState`/`replaceState`,
84
+ * staying reactive to `popstate` events. No router required.
85
+ *
86
+ * @stability Beta
87
+ */
88
+ declare function useBrowserSearchParams(): SearchParamsAdapter;
89
+
90
+ export { DefaultLink as D, type GenesisLinkProps as G, type LinkComponent as L, type NavigateFn as N, type SearchParamsAdapter as S, defaultNavigate as d, useBrowserSearchParams as u };
@@ -1,4 +1,4 @@
1
- import { L as LinkComponent, a as NavigateFn, S as SearchParamsAdapter } from '../navigation-By1DrzaM.js';
1
+ import { L as LinkComponent, N as NavigateFn, S as SearchParamsAdapter } from '../navigation-vmFJ6r1m.js';
2
2
  import 'react';
3
3
 
4
4
  /**
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { isExternalHref } from '../chunk-FUVWKTPL.js';
3
+ import '../chunk-424PT5DM.js';
3
4
  import { useCallback, useMemo } from 'react';
4
5
  import { Link, useNavigate, useSearchParams, useLocation } from 'react-router';
5
6
  import { jsx } from 'react/jsx-runtime';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/router/react-router.tsx"],"names":[],"mappings":";;;;;AA2CO,IAAM,kBAAiC,CAAC,EAAE,IAAA,EAAM,MAAA,EAAQ,GAAG,KAAA,EAAM;AAAA;AAAA;AAAA;AAAA;AAAA,kBAKtE,GAAA,CAAC,IAAA,EAAA,EAAK,EAAA,EAAI,IAAA,EAAO,GAAG,KAAA,EAAQ,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,aAAA,EAAe,MAAA,KAAW,IAAA,EAAO;AAAA;AAYrF,SAAS,sBAAA,GAAqC;AACnD,EAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,EAAA,OAAO,WAAA;AAAA,IACL,CAAC,IAAA,KAAS;AACR,MAAA,IAAI,cAAA,CAAe,IAAI,CAAA,EAAG;AACxB,QAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,MAAA,CAAO,QAAA,CAAS,OAAO,IAAI,CAAA;AAC9D,QAAA;AAAA,MACF;AACA,MAAA,KAAK,SAAS,IAAI,CAAA;AAAA,IACpB,CAAA;AAAA,IACA,CAAC,QAAQ;AAAA,GACX;AACF;AAWO,SAAS,0BAAA,GAAkD;AAChE,EAAA,MAAM,CAAC,YAAY,CAAA,GAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,WAAA,EAAY;AAC7B,EAAA,MAAM,WAAW,WAAA,EAAY;AAE7B,EAAA,MAAM,eAAA,GAAkB,WAAA;AAAA,IACtB,CAAC,MAAM,OAAA,KAAY;AACjB,MAAA,MAAM,EAAA,GAAK,KAAK,QAAA,EAAS;AACzB,MAAA,KAAK,QAAA;AAAA,QACH,EAAE,MAAA,EAAQ,EAAA,GAAK,IAAI,EAAE,CAAA,CAAA,GAAK,IAAI,IAAA,EAAK;AAAA,QACnC,EAAE,OAAA,EAAS,OAAA,EAAS,OAAA;AAAQ,OAC9B;AAAA,IACF,CAAA;AAAA,IACA,CAAC,UAAU,IAAI;AAAA,GACjB;AAEA,EAAA,OAAO,OAAA;AAAA,IACL,OAAO,EAAE,YAAA,EAAc,eAAA,EAAgB,CAAA;AAAA,IACvC,CAAC,cAAc,eAAe;AAAA,GAChC;AACF;AASO,SAAS,wBAAA,GAAmC;AACjD,EAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAQ,IAAA,KAAS,WAAA,EAAY;AAC/C,EAAA,OAAO,CAAA,EAAG,QAAQ,CAAA,EAAG,MAAM,GAAG,IAAI,CAAA,CAAA;AACpC","file":"react-router.js","sourcesContent":["\"use client\";\n\n/**\n * React Router (v7, library or framework mode) adapters for genesis-ui's\n * router-agnostic navigation contract.\n *\n * Import from `@marktiderman/genesis-ui/router/react-router`. This is the only\n * genesis-ui entry that imports `react-router`, and `react-router` is an\n * OPTIONAL peer: apps on another router never install it, and apps that do\n * not import this subpath never load it.\n *\n * ```tsx\n * import { AppShell } from \"@marktiderman/genesis-ui/layout\";\n * import {\n * ReactRouterLink,\n * useReactRouterActivePath,\n * } from \"@marktiderman/genesis-ui/router/react-router\";\n *\n * <AppShell linkComponent={ReactRouterLink} activePath={useReactRouterActivePath()} … />\n * ```\n */\nimport { useCallback, useMemo } from \"react\";\nimport { Link, useLocation, useNavigate, useSearchParams } from \"react-router\";\nimport type {\n LinkComponent,\n NavigateFn,\n SearchParamsAdapter,\n} from \"../navigation\";\nimport { isExternalHref } from \"./utils\";\n\n/**\n * {@link LinkComponent} for React Router. Pass it as `linkComponent` to\n * `AppShell`, `StatCard` and anything else that takes one. `href` maps to\n * React Router's `to` (query and hash included), and `testID` is forwarded as\n * `data-testid`, as `DefaultLink` does.\n *\n * Genesis decides the active state and passes `aria-current` itself. This is\n * a plain `Link`, not a `NavLink`, so the router never adds a second opinion.\n * It forwards `ref` and unknown props, so it works as the child of a Radix\n * `asChild` (`<Button asChild>`).\n *\n * @stability Beta\n */\nexport const ReactRouterLink: LinkComponent = ({ href, testID, ...props }) => (\n // `props` carries `ref` and anything a Radix `asChild` parent merges in;\n // React Router's `Link` forwards both to the anchor. `testID` wins over a\n // `data-testid` in `props` (a parent's may be `undefined`), but only when\n // given, so a parent's real one survives.\n <Link to={href} {...props} {...(testID !== undefined ? { \"data-testid\": testID } : null)} />\n);\n\n/**\n * {@link NavigateFn} backed by React Router's `useNavigate()`. Pass it as\n * `ResourcePage`'s `onNavigate` (or anywhere a `NavigateFn` is accepted) for\n * client-side navigation instead of a full page load. Absolute URLs\n * (`https://…`, `mailto:…`, `//host/…`) leave the app with a document\n * navigation, as `defaultNavigate` does.\n *\n * @stability Beta\n */\nexport function useReactRouterNavigate(): NavigateFn {\n const navigate = useNavigate();\n return useCallback<NavigateFn>(\n (path) => {\n if (isExternalHref(path)) {\n if (typeof window !== \"undefined\") window.location.assign(path);\n return;\n }\n void navigate(path);\n },\n [navigate],\n );\n}\n\n/**\n * {@link SearchParamsAdapter} backed by React Router's location. Pass it as\n * `useDataFilters(key, defs, { searchParams })` so URL-synced filters go\n * through the router (loaders re-run, back/forward stays in sync) instead of\n * the raw History API. Like the default browser adapter, an update keeps the\n * current `#hash`.\n *\n * @stability Beta\n */\nexport function useReactRouterSearchParams(): SearchParamsAdapter {\n const [searchParams] = useSearchParams();\n const { hash } = useLocation();\n const navigate = useNavigate();\n\n const setSearchParams = useCallback<SearchParamsAdapter[\"setSearchParams\"]>(\n (next, options) => {\n const qs = next.toString();\n void navigate(\n { search: qs ? `?${qs}` : \"\", hash },\n { replace: options?.replace },\n );\n },\n [navigate, hash],\n );\n\n return useMemo(\n () => ({ searchParams, setSearchParams }),\n [searchParams, setSearchParams],\n );\n}\n\n/**\n * The current location as `pathname + search + hash`, the shape `AppShell`'s\n * `activePath` expects, so nav items whose `to` names a query or hash\n * (`/admin?tab=users`, `/settings#profile`) highlight correctly.\n *\n * @stability Beta\n */\nexport function useReactRouterActivePath(): string {\n const { pathname, search, hash } = useLocation();\n return `${pathname}${search}${hash}`;\n}\n"]}
1
+ {"version":3,"sources":["../../src/router/react-router.tsx"],"names":[],"mappings":";;;;;;AA2CO,IAAM,kBAAiC,CAAC,EAAE,IAAA,EAAM,MAAA,EAAQ,GAAG,KAAA,EAAM;AAAA;AAAA;AAAA;AAAA;AAAA,kBAKtE,GAAA,CAAC,IAAA,EAAA,EAAK,EAAA,EAAI,IAAA,EAAO,GAAG,KAAA,EAAQ,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,aAAA,EAAe,MAAA,KAAW,IAAA,EAAO;AAAA;AAYrF,SAAS,sBAAA,GAAqC;AACnD,EAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,EAAA,OAAO,WAAA;AAAA,IACL,CAAC,IAAA,KAAS;AACR,MAAA,IAAI,cAAA,CAAe,IAAI,CAAA,EAAG;AACxB,QAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,MAAA,CAAO,QAAA,CAAS,OAAO,IAAI,CAAA;AAC9D,QAAA;AAAA,MACF;AACA,MAAA,KAAK,SAAS,IAAI,CAAA;AAAA,IACpB,CAAA;AAAA,IACA,CAAC,QAAQ;AAAA,GACX;AACF;AAWO,SAAS,0BAAA,GAAkD;AAChE,EAAA,MAAM,CAAC,YAAY,CAAA,GAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,WAAA,EAAY;AAC7B,EAAA,MAAM,WAAW,WAAA,EAAY;AAE7B,EAAA,MAAM,eAAA,GAAkB,WAAA;AAAA,IACtB,CAAC,MAAM,OAAA,KAAY;AACjB,MAAA,MAAM,EAAA,GAAK,KAAK,QAAA,EAAS;AACzB,MAAA,KAAK,QAAA;AAAA,QACH,EAAE,MAAA,EAAQ,EAAA,GAAK,IAAI,EAAE,CAAA,CAAA,GAAK,IAAI,IAAA,EAAK;AAAA,QACnC,EAAE,OAAA,EAAS,OAAA,EAAS,OAAA;AAAQ,OAC9B;AAAA,IACF,CAAA;AAAA,IACA,CAAC,UAAU,IAAI;AAAA,GACjB;AAEA,EAAA,OAAO,OAAA;AAAA,IACL,OAAO,EAAE,YAAA,EAAc,eAAA,EAAgB,CAAA;AAAA,IACvC,CAAC,cAAc,eAAe;AAAA,GAChC;AACF;AASO,SAAS,wBAAA,GAAmC;AACjD,EAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAQ,IAAA,KAAS,WAAA,EAAY;AAC/C,EAAA,OAAO,CAAA,EAAG,QAAQ,CAAA,EAAG,MAAM,GAAG,IAAI,CAAA,CAAA;AACpC","file":"react-router.js","sourcesContent":["\"use client\";\n\n/**\n * React Router (v7, library or framework mode) adapters for genesis-ui's\n * router-agnostic navigation contract.\n *\n * Import from `@marktiderman/genesis-ui/router/react-router`. This is the only\n * genesis-ui entry that imports `react-router`, and `react-router` is an\n * OPTIONAL peer: apps on another router never install it, and apps that do\n * not import this subpath never load it.\n *\n * ```tsx\n * import { AppShell } from \"@marktiderman/genesis-ui/layout\";\n * import {\n * ReactRouterLink,\n * useReactRouterActivePath,\n * } from \"@marktiderman/genesis-ui/router/react-router\";\n *\n * <AppShell linkComponent={ReactRouterLink} activePath={useReactRouterActivePath()} … />\n * ```\n */\nimport { useCallback, useMemo } from \"react\";\nimport { Link, useLocation, useNavigate, useSearchParams } from \"react-router\";\nimport type {\n LinkComponent,\n NavigateFn,\n SearchParamsAdapter,\n} from \"../navigation\";\nimport { isExternalHref } from \"./utils\";\n\n/**\n * {@link LinkComponent} for React Router. Pass it as `linkComponent` to\n * `AppShell`, `StatCard` and anything else that takes one. `href` maps to\n * React Router's `to` (query and hash included), and `testID` is forwarded as\n * `data-testid`, as `DefaultLink` does.\n *\n * Genesis decides the active state and passes `aria-current` itself. This is\n * a plain `Link`, not a `NavLink`, so the router never adds a second opinion.\n * It forwards `ref` and unknown props, so it works as the child of a Radix\n * `asChild` (`<Button asChild>`).\n *\n * @stability Beta\n */\nexport const ReactRouterLink: LinkComponent = ({ href, testID, ...props }) => (\n // `props` carries `ref` and anything a Radix `asChild` parent merges in;\n // React Router's `Link` forwards both to the anchor. `testID` wins over a\n // `data-testid` in `props` (a parent's may be `undefined`), but only when\n // given, so a parent's real one survives.\n <Link to={href} {...props} {...(testID !== undefined ? { \"data-testid\": testID } : null)} />\n);\n\n/**\n * {@link NavigateFn} backed by React Router's `useNavigate()`. Pass it as\n * `ResourcePage`'s `onNavigate` (or anywhere a `NavigateFn` is accepted) for\n * client-side navigation instead of a full page load. Absolute URLs\n * (`https://…`, `mailto:…`, `//host/…`) leave the app with a document\n * navigation, as `defaultNavigate` does.\n *\n * @stability Beta\n */\nexport function useReactRouterNavigate(): NavigateFn {\n const navigate = useNavigate();\n return useCallback<NavigateFn>(\n (path) => {\n if (isExternalHref(path)) {\n if (typeof window !== \"undefined\") window.location.assign(path);\n return;\n }\n void navigate(path);\n },\n [navigate],\n );\n}\n\n/**\n * {@link SearchParamsAdapter} backed by React Router's location. Pass it as\n * `useDataFilters(key, defs, { searchParams })` so URL-synced filters go\n * through the router (loaders re-run, back/forward stays in sync) instead of\n * the raw History API. Like the default browser adapter, an update keeps the\n * current `#hash`.\n *\n * @stability Beta\n */\nexport function useReactRouterSearchParams(): SearchParamsAdapter {\n const [searchParams] = useSearchParams();\n const { hash } = useLocation();\n const navigate = useNavigate();\n\n const setSearchParams = useCallback<SearchParamsAdapter[\"setSearchParams\"]>(\n (next, options) => {\n const qs = next.toString();\n void navigate(\n { search: qs ? `?${qs}` : \"\", hash },\n { replace: options?.replace },\n );\n },\n [navigate, hash],\n );\n\n return useMemo(\n () => ({ searchParams, setSearchParams }),\n [searchParams, setSearchParams],\n );\n}\n\n/**\n * The current location as `pathname + search + hash`, the shape `AppShell`'s\n * `activePath` expects, so nav items whose `to` names a query or hash\n * (`/admin?tab=users`, `/settings#profile`) highlight correctly.\n *\n * @stability Beta\n */\nexport function useReactRouterActivePath(): string {\n const { pathname, search, hash } = useLocation();\n return `${pathname}${search}${hash}`;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { L as LinkComponent, a as NavigateFn, S as SearchParamsAdapter } from '../navigation-By1DrzaM.js';
1
+ import { L as LinkComponent, N as NavigateFn, S as SearchParamsAdapter } from '../navigation-vmFJ6r1m.js';
2
2
  import 'react';
3
3
 
4
4
  /**
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { isExternalHref, splitNavigationHref } from '../chunk-FUVWKTPL.js';
3
- import { DefaultLink } from '../chunk-KC43CNMA.js';
3
+ import { DefaultLink } from '../chunk-J45FHL7D.js';
4
+ import '../chunk-424PT5DM.js';
4
5
  import { useCallback, useMemo } from 'react';
5
6
  import { useRouter, useRouterState, useLinkProps } from '@tanstack/react-router';
6
7
  import { jsx } from 'react/jsx-runtime';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/router/tanstack-router.tsx"],"names":[],"mappings":"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","file":"tanstack-router.js","sourcesContent":["\"use client\";\n\n/**\n * TanStack Router adapters for genesis-ui's router-agnostic navigation\n * contract.\n *\n * Import from `@marktiderman/genesis-ui/router/tanstack-router`. This is the\n * only genesis-ui entry that imports `@tanstack/react-router`, and that\n * package is an OPTIONAL peer: apps on another router never install it, and\n * apps that do not import this subpath never load it.\n *\n * Genesis hands links a plain string `href` (`/admin?tab=users#top`), while\n * TanStack Router wants `to`, `search` and `hash` separately. These adapters\n * do that split, and they parse the query with the router's OWN\n * `parseSearch`, so a custom search serializer and `validateSearch` see the\n * same values a typed `<Link search={…}>` would produce.\n *\n * ```tsx\n * import { AppShell } from \"@marktiderman/genesis-ui/layout\";\n * import {\n * TanStackRouterLink,\n * useTanStackRouterActivePath,\n * } from \"@marktiderman/genesis-ui/router/tanstack-router\";\n *\n * <AppShell linkComponent={TanStackRouterLink} activePath={useTanStackRouterActivePath()} … />\n * ```\n */\nimport { useCallback, useMemo, type AnchorHTMLAttributes, type Ref } from \"react\";\nimport {\n useLinkProps,\n useRouter,\n useRouterState,\n type AnyRouter,\n} from \"@tanstack/react-router\";\nimport {\n DefaultLink,\n type GenesisLinkProps,\n type LinkComponent,\n type NavigateFn,\n type SearchParamsAdapter,\n} from \"../navigation\";\nimport { isExternalHref, splitNavigationHref } from \"./utils\";\n\n// Genesis owns the active state (it passes `aria-current` itself, computed by\n// `isPathActive` or the app's `isActive`). TanStack's Link adds its own —\n// `aria-current=\"page\"`, `data-status=\"active\"` and an `active` class —\n// whenever ITS prefix match passes, which can disagree with Genesis (an\n// exact-match `/app` item, or a `?tab=` item) and would leave two items\n// announced as the current page. An empty `activeProps` stops the class;\n// the attributes are overridden below.\nconst NO_ACTIVE_PROPS = {};\n\n/**\n * The navigation target for a Genesis href, with the query parsed by the\n * router's own `parseSearch` (what `<Link search>` expects).\n */\nfunction toLocation(router: AnyRouter, href: string) {\n const { pathname, search, hash } = splitNavigationHref(href);\n return {\n // `\"\"` (a bare `?q=1` or `#x`) means \"this page\" — TanStack's `.`.\n to: pathname || \".\",\n search: search ? router.options.parseSearch(`?${search}`) : undefined,\n hash: hash || undefined,\n };\n}\n\nfunction InAppLink({\n href,\n testID,\n children,\n \"aria-current\": ariaCurrent,\n ref,\n ...rest\n}: GenesisLinkProps & { ref?: Ref<HTMLAnchorElement> }) {\n const router = useRouter();\n const target = useMemo(() => toLocation(router, href), [router, href]);\n // `useLinkProps` is the hook behind TanStack's `<Link>`: client-side\n // navigation on click, `href` built through the router (basepath, rewrites,\n // masks), preloading per the router's `defaultPreload`. Props it does not\n // know (anything a Radix `asChild` parent merges in) pass through to the\n // anchor, and `ref` is merged with the one it needs for preloading.\n const linkProps = useLinkProps(\n {\n ...target,\n ...rest,\n activeProps: NO_ACTIVE_PROPS,\n // The adapter is route-tree agnostic, so it cannot satisfy the generated\n // route types; the target is checked at runtime by the router instead.\n } as Parameters<typeof useLinkProps>[0],\n ref as Parameters<typeof useLinkProps>[1],\n ) as AnchorHTMLAttributes<HTMLAnchorElement> & {\n \"data-status\"?: string;\n ref?: Ref<HTMLAnchorElement>;\n };\n const { \"data-status\": _status, ...anchorProps } = linkProps;\n return (\n <a\n {...anchorProps}\n aria-current={ariaCurrent}\n // Only when given, so a `data-testid` merged in by an `asChild` parent survives.\n {...(testID !== undefined ? { \"data-testid\": testID } : null)}\n >\n {children}\n </a>\n );\n}\n\n/**\n * {@link LinkComponent} for TanStack Router. Pass it as `linkComponent` to\n * `AppShell`, `StatCard` and anything else that takes one. In-app hrefs\n * navigate client-side (query and hash included, parsed by the router's\n * `parseSearch`); absolute URLs render a plain anchor. `testID` is forwarded\n * as `data-testid`, as `DefaultLink` does, and the active state stays the one\n * Genesis passes: TanStack's own `aria-current` / `active` class are not\n * applied. It forwards `ref` and unknown props, so it works as the child of a\n * Radix `asChild` (`<Button asChild>`).\n *\n * @stability Beta\n */\nexport const TanStackRouterLink: LinkComponent = (props) =>\n isExternalHref(props.href) ? <DefaultLink {...props} /> : <InAppLink {...props} />;\n\n/**\n * {@link NavigateFn} backed by the TanStack router. Pass it as\n * `ResourcePage`'s `onNavigate` (or anywhere a `NavigateFn` is accepted) for\n * client-side navigation instead of a full page load. Absolute URLs\n * (`https://…`, `mailto:…`, `//host/…`) leave the app with a document\n * navigation, as `defaultNavigate` does.\n *\n * @stability Beta\n */\nexport function useTanStackRouterNavigate(): NavigateFn {\n const router = useRouter();\n return useCallback<NavigateFn>(\n (path) => {\n if (isExternalHref(path)) {\n if (typeof window !== \"undefined\") window.location.assign(path);\n return;\n }\n void router.navigate(\n toLocation(router, path) as Parameters<AnyRouter[\"navigate\"]>[0],\n );\n },\n [router],\n );\n}\n\n/**\n * {@link SearchParamsAdapter} backed by the TanStack router's location. Pass\n * it as `useDataFilters(key, defs, { searchParams })` so URL-synced filters go\n * through the router (loaders and `validateSearch` run, back/forward stays in\n * sync) instead of the raw History API. Like the default browser adapter, an\n * update keeps the current path and `#hash`.\n *\n * @stability Beta\n */\nexport function useTanStackRouterSearchParams(): SearchParamsAdapter {\n const router = useRouter();\n const searchStr = useRouterState({\n select: (state) => state.location.searchStr,\n });\n const searchParams = useMemo(\n () => new URLSearchParams(searchStr),\n [searchStr],\n );\n\n const setSearchParams = useCallback<SearchParamsAdapter[\"setSearchParams\"]>(\n (next, options) => {\n const qs = next.toString();\n void router.navigate({\n // The current URL path, not `.`: `.` resolves against the matched\n // route's template, which is not this URL under a splat or\n // not-found route.\n to: router.state.location.pathname,\n search: router.options.parseSearch(qs ? `?${qs}` : \"\"),\n hash: true,\n replace: options?.replace,\n } as Parameters<AnyRouter[\"navigate\"]>[0]);\n },\n [router],\n );\n\n return useMemo(\n () => ({ searchParams, setSearchParams }),\n [searchParams, setSearchParams],\n );\n}\n\n/**\n * The current location as `pathname + search + hash`, the shape `AppShell`'s\n * `activePath` expects, so nav items whose `to` names a query or hash\n * (`/admin?tab=users`, `/settings#profile`) highlight correctly. Paths are the\n * router's (basepath removed), matching the `to` your nav items use.\n *\n * @stability Beta\n */\nexport function useTanStackRouterActivePath(): string {\n return useRouterState({\n select: ({ location }) => {\n const search =\n location.searchStr && !location.searchStr.startsWith(\"?\")\n ? `?${location.searchStr}`\n : location.searchStr;\n return `${location.pathname}${search}${location.hash ? `#${location.hash}` : \"\"}`;\n },\n });\n}\n"]}
1
+ {"version":3,"sources":["../../src/router/tanstack-router.tsx"],"names":[],"mappings":"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","file":"tanstack-router.js","sourcesContent":["\"use client\";\n\n/**\n * TanStack Router adapters for genesis-ui's router-agnostic navigation\n * contract.\n *\n * Import from `@marktiderman/genesis-ui/router/tanstack-router`. This is the\n * only genesis-ui entry that imports `@tanstack/react-router`, and that\n * package is an OPTIONAL peer: apps on another router never install it, and\n * apps that do not import this subpath never load it.\n *\n * Genesis hands links a plain string `href` (`/admin?tab=users#top`), while\n * TanStack Router wants `to`, `search` and `hash` separately. These adapters\n * do that split, and they parse the query with the router's OWN\n * `parseSearch`, so a custom search serializer and `validateSearch` see the\n * same values a typed `<Link search={…}>` would produce.\n *\n * ```tsx\n * import { AppShell } from \"@marktiderman/genesis-ui/layout\";\n * import {\n * TanStackRouterLink,\n * useTanStackRouterActivePath,\n * } from \"@marktiderman/genesis-ui/router/tanstack-router\";\n *\n * <AppShell linkComponent={TanStackRouterLink} activePath={useTanStackRouterActivePath()} … />\n * ```\n */\nimport { useCallback, useMemo, type AnchorHTMLAttributes, type Ref } from \"react\";\nimport {\n useLinkProps,\n useRouter,\n useRouterState,\n type AnyRouter,\n} from \"@tanstack/react-router\";\nimport {\n DefaultLink,\n type GenesisLinkProps,\n type LinkComponent,\n type NavigateFn,\n type SearchParamsAdapter,\n} from \"../navigation\";\nimport { isExternalHref, splitNavigationHref } from \"./utils\";\n\n// Genesis owns the active state (it passes `aria-current` itself, computed by\n// `isPathActive` or the app's `isActive`). TanStack's Link adds its own —\n// `aria-current=\"page\"`, `data-status=\"active\"` and an `active` class —\n// whenever ITS prefix match passes, which can disagree with Genesis (an\n// exact-match `/app` item, or a `?tab=` item) and would leave two items\n// announced as the current page. An empty `activeProps` stops the class;\n// the attributes are overridden below.\nconst NO_ACTIVE_PROPS = {};\n\n/**\n * The navigation target for a Genesis href, with the query parsed by the\n * router's own `parseSearch` (what `<Link search>` expects).\n */\nfunction toLocation(router: AnyRouter, href: string) {\n const { pathname, search, hash } = splitNavigationHref(href);\n return {\n // `\"\"` (a bare `?q=1` or `#x`) means \"this page\" — TanStack's `.`.\n to: pathname || \".\",\n search: search ? router.options.parseSearch(`?${search}`) : undefined,\n hash: hash || undefined,\n };\n}\n\nfunction InAppLink({\n href,\n testID,\n children,\n \"aria-current\": ariaCurrent,\n ref,\n ...rest\n}: GenesisLinkProps & { ref?: Ref<HTMLAnchorElement> }) {\n const router = useRouter();\n const target = useMemo(() => toLocation(router, href), [router, href]);\n // `useLinkProps` is the hook behind TanStack's `<Link>`: client-side\n // navigation on click, `href` built through the router (basepath, rewrites,\n // masks), preloading per the router's `defaultPreload`. Props it does not\n // know (anything a Radix `asChild` parent merges in) pass through to the\n // anchor, and `ref` is merged with the one it needs for preloading.\n const linkProps = useLinkProps(\n {\n ...target,\n ...rest,\n activeProps: NO_ACTIVE_PROPS,\n // The adapter is route-tree agnostic, so it cannot satisfy the generated\n // route types; the target is checked at runtime by the router instead.\n } as Parameters<typeof useLinkProps>[0],\n ref as Parameters<typeof useLinkProps>[1],\n ) as AnchorHTMLAttributes<HTMLAnchorElement> & {\n \"data-status\"?: string;\n ref?: Ref<HTMLAnchorElement>;\n };\n const { \"data-status\": _status, ...anchorProps } = linkProps;\n return (\n <a\n {...anchorProps}\n aria-current={ariaCurrent}\n // Only when given, so a `data-testid` merged in by an `asChild` parent survives.\n {...(testID !== undefined ? { \"data-testid\": testID } : null)}\n >\n {children}\n </a>\n );\n}\n\n/**\n * {@link LinkComponent} for TanStack Router. Pass it as `linkComponent` to\n * `AppShell`, `StatCard` and anything else that takes one. In-app hrefs\n * navigate client-side (query and hash included, parsed by the router's\n * `parseSearch`); absolute URLs render a plain anchor. `testID` is forwarded\n * as `data-testid`, as `DefaultLink` does, and the active state stays the one\n * Genesis passes: TanStack's own `aria-current` / `active` class are not\n * applied. It forwards `ref` and unknown props, so it works as the child of a\n * Radix `asChild` (`<Button asChild>`).\n *\n * @stability Beta\n */\nexport const TanStackRouterLink: LinkComponent = (props) =>\n isExternalHref(props.href) ? <DefaultLink {...props} /> : <InAppLink {...props} />;\n\n/**\n * {@link NavigateFn} backed by the TanStack router. Pass it as\n * `ResourcePage`'s `onNavigate` (or anywhere a `NavigateFn` is accepted) for\n * client-side navigation instead of a full page load. Absolute URLs\n * (`https://…`, `mailto:…`, `//host/…`) leave the app with a document\n * navigation, as `defaultNavigate` does.\n *\n * @stability Beta\n */\nexport function useTanStackRouterNavigate(): NavigateFn {\n const router = useRouter();\n return useCallback<NavigateFn>(\n (path) => {\n if (isExternalHref(path)) {\n if (typeof window !== \"undefined\") window.location.assign(path);\n return;\n }\n void router.navigate(\n toLocation(router, path) as Parameters<AnyRouter[\"navigate\"]>[0],\n );\n },\n [router],\n );\n}\n\n/**\n * {@link SearchParamsAdapter} backed by the TanStack router's location. Pass\n * it as `useDataFilters(key, defs, { searchParams })` so URL-synced filters go\n * through the router (loaders and `validateSearch` run, back/forward stays in\n * sync) instead of the raw History API. Like the default browser adapter, an\n * update keeps the current path and `#hash`.\n *\n * @stability Beta\n */\nexport function useTanStackRouterSearchParams(): SearchParamsAdapter {\n const router = useRouter();\n const searchStr = useRouterState({\n select: (state) => state.location.searchStr,\n });\n const searchParams = useMemo(\n () => new URLSearchParams(searchStr),\n [searchStr],\n );\n\n const setSearchParams = useCallback<SearchParamsAdapter[\"setSearchParams\"]>(\n (next, options) => {\n const qs = next.toString();\n void router.navigate({\n // The current URL path, not `.`: `.` resolves against the matched\n // route's template, which is not this URL under a splat or\n // not-found route.\n to: router.state.location.pathname,\n search: router.options.parseSearch(qs ? `?${qs}` : \"\"),\n hash: true,\n replace: options?.replace,\n } as Parameters<AnyRouter[\"navigate\"]>[0]);\n },\n [router],\n );\n\n return useMemo(\n () => ({ searchParams, setSearchParams }),\n [searchParams, setSearchParams],\n );\n}\n\n/**\n * The current location as `pathname + search + hash`, the shape `AppShell`'s\n * `activePath` expects, so nav items whose `to` names a query or hash\n * (`/admin?tab=users`, `/settings#profile`) highlight correctly. Paths are the\n * router's (basepath removed), matching the `to` your nav items use.\n *\n * @stability Beta\n */\nexport function useTanStackRouterActivePath(): string {\n return useRouterState({\n select: ({ location }) => {\n const search =\n location.searchStr && !location.searchStr.startsWith(\"?\")\n ? `?${location.searchStr}`\n : location.searchStr;\n return `${location.pathname}${search}${location.hash ? `#${location.hash}` : \"\"}`;\n },\n });\n}\n"]}
@@ -1,3 +1,5 @@
1
+ import './chunk-424PT5DM.js';
2
+
1
3
  // src/status-colors.ts
2
4
  var prdStatusColors = {
3
5
  idea: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/status-colors.ts"],"names":[],"mappings":";AAQO,IAAM,eAAA,GAA0C;AAAA,EACrD,IAAA,EAAM,+DAAA;AAAA,EACN,QAAA,EAAU,mEAAA;AAAA,EACV,MAAA,EAAQ,kEAAA;AAAA,EACR,MAAA,EAAQ,0EAAA;AAAA,EACR,QAAA,EAAU,sEAAA;AAAA,EACV,OAAA,EAAS;AACX;AAGO,IAAM,gBAAA,GAA2C;AAAA,EACtD,IAAA,EAAM,+DAAA;AAAA,EACN,WAAA,EAAa,sEAAA;AAAA,EACb,SAAA,EAAW,kEAAA;AAAA,EACX,IAAA,EAAM,8EAAA;AAAA,EACN,OAAA,EAAS;AACX;AAGO,IAAM,mBAAA,GAA8C;AAAA,EACzD,UAAA,EAAY,8EAAA;AAAA,EACZ,MAAA,EAAQ,kEAAA;AAAA,EACR,MAAA,EAAQ;AACV;AAGO,IAAM,gBAAA,GAA2C;AAAA,EACtD,GAAA,EAAK,+DAAA;AAAA,EACL,QAAA,EAAU,kEAAA;AAAA,EACV,QAAA,EAAU,8EAAA;AAAA,EACV,SAAA,EAAW;AACb;AAGO,IAAM,oBAAA,GAA+C;AAAA,EAC1D,GAAA,EAAK,+DAAA;AAAA,EACL,QAAA,EAAU,kEAAA;AAAA,EACV,QAAA,EAAU,8EAAA;AAAA,EACV,QAAA,EAAU;AACZ;AAGO,IAAM,mBAAA,GAA8C;AAAA,EACzD,OAAA,EAAS,8EAAA;AAAA,EACT,GAAA,EAAK,kEAAA;AAAA,EACL,WAAA,EAAa,sEAAA;AAAA,EACb,QAAA,EAAU;AACZ;AAGO,IAAM,oBAAA,GAA+C;AAAA,EAC1D,SAAA,EAAW,8EAAA;AAAA,EACX,WAAA,EAAa,kEAAA;AAAA,EACb,OAAA,EAAS,sEAAA;AAAA,EACT,OAAA,EAAS;AACX;AAGO,IAAM,gBAAA,GAA2C;AAAA,EACtD,MAAA,EAAQ,8EAAA;AAAA,EACR,UAAA,EAAY,sEAAA;AAAA,EACZ,MAAA,EAAQ,sEAAA;AAAA,EACR,QAAA,EAAU;AACZ;AAGO,SAAS,iBAAiB,GAAA,EAAqB;AACpD,EAAA,IAAI,GAAA,IAAO,IAAI,OAAO,gBAAA;AACtB,EAAA,IAAI,GAAA,IAAO,IAAI,OAAO,cAAA;AACtB,EAAA,OAAO,YAAA;AACT;AAEO,SAAS,qBAAqB,GAAA,EAAqB;AACxD,EAAA,IAAI,GAAA,IAAO,IAAI,OAAO,wCAAA;AACtB,EAAA,IAAI,GAAA,IAAO,IAAI,OAAO,oCAAA;AACtB,EAAA,OAAO,gCAAA;AACT;AAGO,IAAM,eAAA,GAA0C;AAAA,EACrD,GAAG,eAAA;AAAA,EACH,GAAG,gBAAA;AAAA,EACH,GAAG,mBAAA;AAAA,EACH,GAAG,gBAAA;AAAA,EACH,GAAG;AACL","file":"status-colors.js","sourcesContent":["/**\n * Shared status color mappings\n *\n * Single source of truth for all status → color badge mappings.\n * Used across Command Center, Product Detail, Task Board, Roadmap, etc.\n */\n\n/** PRD statuses */\nexport const prdStatusColors: Record<string, string> = {\n idea: \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400\",\n drafting: \"bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-400\",\n active: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n locked: \"bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400\",\n building: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n shipped: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n};\n\n/** Task statuses */\nexport const taskStatusColors: Record<string, string> = {\n todo: \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400\",\n in_progress: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n in_review: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n done: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n blocked: \"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400\",\n};\n\n/** Product statuses */\nexport const productStatusColors: Record<string, string> = {\n production: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n active: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n design: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n};\n\n/** Idea statuses */\nexport const ideaStatusColors: Record<string, string> = {\n new: \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400\",\n reviewed: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n promoted: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n dismissed: \"bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400\",\n};\n\n/** Feedback statuses */\nexport const feedbackStatusColors: Record<string, string> = {\n new: \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400\",\n reviewed: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n promoted: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n archived: \"bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400\",\n};\n\n/** Changelog entry types */\nexport const changelogTypeColors: Record<string, string> = {\n feature: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n fix: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n improvement: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n breaking: \"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400\",\n};\n\n/** Research recommended actions */\nexport const researchActionColors: Record<string, string> = {\n implement: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n investigate: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n monitor: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n dismiss: \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400\",\n};\n\n/** Sync statuses */\nexport const syncStatusColors: Record<string, string> = {\n synced: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n pending_pr: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n behind: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n diverged: \"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400\",\n};\n\n/** Coverage color helpers */\nexport function getCoverageColor(pct: number): string {\n if (pct >= 70) return \"bg-emerald-500\";\n if (pct >= 30) return \"bg-amber-500\";\n return \"bg-red-500\";\n}\n\nexport function getCoverageTextColor(pct: number): string {\n if (pct >= 70) return \"text-emerald-600 dark:text-emerald-400\";\n if (pct >= 30) return \"text-amber-600 dark:text-amber-400\";\n return \"text-red-600 dark:text-red-400\";\n}\n\n/** Combined lookup — useful when you don't know the entity type */\nexport const allStatusColors: Record<string, string> = {\n ...prdStatusColors,\n ...taskStatusColors,\n ...productStatusColors,\n ...ideaStatusColors,\n ...feedbackStatusColors,\n};\n"]}
1
+ {"version":3,"sources":["../src/status-colors.ts"],"names":[],"mappings":";;;AAQO,IAAM,eAAA,GAA0C;AAAA,EACrD,IAAA,EAAM,+DAAA;AAAA,EACN,QAAA,EAAU,mEAAA;AAAA,EACV,MAAA,EAAQ,kEAAA;AAAA,EACR,MAAA,EAAQ,0EAAA;AAAA,EACR,QAAA,EAAU,sEAAA;AAAA,EACV,OAAA,EAAS;AACX;AAGO,IAAM,gBAAA,GAA2C;AAAA,EACtD,IAAA,EAAM,+DAAA;AAAA,EACN,WAAA,EAAa,sEAAA;AAAA,EACb,SAAA,EAAW,kEAAA;AAAA,EACX,IAAA,EAAM,8EAAA;AAAA,EACN,OAAA,EAAS;AACX;AAGO,IAAM,mBAAA,GAA8C;AAAA,EACzD,UAAA,EAAY,8EAAA;AAAA,EACZ,MAAA,EAAQ,kEAAA;AAAA,EACR,MAAA,EAAQ;AACV;AAGO,IAAM,gBAAA,GAA2C;AAAA,EACtD,GAAA,EAAK,+DAAA;AAAA,EACL,QAAA,EAAU,kEAAA;AAAA,EACV,QAAA,EAAU,8EAAA;AAAA,EACV,SAAA,EAAW;AACb;AAGO,IAAM,oBAAA,GAA+C;AAAA,EAC1D,GAAA,EAAK,+DAAA;AAAA,EACL,QAAA,EAAU,kEAAA;AAAA,EACV,QAAA,EAAU,8EAAA;AAAA,EACV,QAAA,EAAU;AACZ;AAGO,IAAM,mBAAA,GAA8C;AAAA,EACzD,OAAA,EAAS,8EAAA;AAAA,EACT,GAAA,EAAK,kEAAA;AAAA,EACL,WAAA,EAAa,sEAAA;AAAA,EACb,QAAA,EAAU;AACZ;AAGO,IAAM,oBAAA,GAA+C;AAAA,EAC1D,SAAA,EAAW,8EAAA;AAAA,EACX,WAAA,EAAa,kEAAA;AAAA,EACb,OAAA,EAAS,sEAAA;AAAA,EACT,OAAA,EAAS;AACX;AAGO,IAAM,gBAAA,GAA2C;AAAA,EACtD,MAAA,EAAQ,8EAAA;AAAA,EACR,UAAA,EAAY,sEAAA;AAAA,EACZ,MAAA,EAAQ,sEAAA;AAAA,EACR,QAAA,EAAU;AACZ;AAGO,SAAS,iBAAiB,GAAA,EAAqB;AACpD,EAAA,IAAI,GAAA,IAAO,IAAI,OAAO,gBAAA;AACtB,EAAA,IAAI,GAAA,IAAO,IAAI,OAAO,cAAA;AACtB,EAAA,OAAO,YAAA;AACT;AAEO,SAAS,qBAAqB,GAAA,EAAqB;AACxD,EAAA,IAAI,GAAA,IAAO,IAAI,OAAO,wCAAA;AACtB,EAAA,IAAI,GAAA,IAAO,IAAI,OAAO,oCAAA;AACtB,EAAA,OAAO,gCAAA;AACT;AAGO,IAAM,eAAA,GAA0C;AAAA,EACrD,GAAG,eAAA;AAAA,EACH,GAAG,gBAAA;AAAA,EACH,GAAG,mBAAA;AAAA,EACH,GAAG,gBAAA;AAAA,EACH,GAAG;AACL","file":"status-colors.js","sourcesContent":["/**\n * Shared status color mappings\n *\n * Single source of truth for all status → color badge mappings.\n * Used across Command Center, Product Detail, Task Board, Roadmap, etc.\n */\n\n/** PRD statuses */\nexport const prdStatusColors: Record<string, string> = {\n idea: \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400\",\n drafting: \"bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-400\",\n active: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n locked: \"bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400\",\n building: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n shipped: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n};\n\n/** Task statuses */\nexport const taskStatusColors: Record<string, string> = {\n todo: \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400\",\n in_progress: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n in_review: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n done: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n blocked: \"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400\",\n};\n\n/** Product statuses */\nexport const productStatusColors: Record<string, string> = {\n production: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n active: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n design: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n};\n\n/** Idea statuses */\nexport const ideaStatusColors: Record<string, string> = {\n new: \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400\",\n reviewed: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n promoted: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n dismissed: \"bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400\",\n};\n\n/** Feedback statuses */\nexport const feedbackStatusColors: Record<string, string> = {\n new: \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400\",\n reviewed: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n promoted: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n archived: \"bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400\",\n};\n\n/** Changelog entry types */\nexport const changelogTypeColors: Record<string, string> = {\n feature: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n fix: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n improvement: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n breaking: \"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400\",\n};\n\n/** Research recommended actions */\nexport const researchActionColors: Record<string, string> = {\n implement: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n investigate: \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\",\n monitor: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n dismiss: \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400\",\n};\n\n/** Sync statuses */\nexport const syncStatusColors: Record<string, string> = {\n synced: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\",\n pending_pr: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n behind: \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400\",\n diverged: \"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400\",\n};\n\n/** Coverage color helpers */\nexport function getCoverageColor(pct: number): string {\n if (pct >= 70) return \"bg-emerald-500\";\n if (pct >= 30) return \"bg-amber-500\";\n return \"bg-red-500\";\n}\n\nexport function getCoverageTextColor(pct: number): string {\n if (pct >= 70) return \"text-emerald-600 dark:text-emerald-400\";\n if (pct >= 30) return \"text-amber-600 dark:text-amber-400\";\n return \"text-red-600 dark:text-red-400\";\n}\n\n/** Combined lookup — useful when you don't know the entity type */\nexport const allStatusColors: Record<string, string> = {\n ...prdStatusColors,\n ...taskStatusColors,\n ...productStatusColors,\n ...ideaStatusColors,\n ...feedbackStatusColors,\n};\n"]}
@@ -8,11 +8,11 @@ import { V as ViewMode } from './view-toggle-CahQ87PW.js';
8
8
  import { Density } from './hooks/use-view-settings.js';
9
9
  import { WizardStep, MutationMode, UndoableWrite, UseResourceOptions } from '@marktiderman/genesis-core/hooks';
10
10
  import { ResourceFormFieldDef } from '@marktiderman/genesis-core';
11
- import { b as Slots, a as SlotPropsFor } from './slots-tR0wf89f.js';
11
+ import { Slots, SlotPropsFor } from '@marktiderman/genesis-core/slots';
12
12
  import { ViewModeDefault } from './hooks/use-view-preference.js';
13
13
  import { ResourceUrlStateOptions } from './hooks/use-resource-url-state.js';
14
14
  import { FilterParam, SortParam } from '@marktiderman/genesis-core/provider';
15
- import { a as NavigateFn, S as SearchParamsAdapter } from './navigation-By1DrzaM.js';
15
+ import { N as NavigateFn, S as SearchParamsAdapter } from './navigation-vmFJ6r1m.js';
16
16
 
17
17
  interface DataPageShellProps {
18
18
  /** Display title for the page */
package/dist/utils.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { cn, formatDate, formatRelativeTime } from './chunk-NZ5WBA4R.js';
2
+ import './chunk-424PT5DM.js';
2
3
  //# sourceMappingURL=utils.js.map
3
4
  //# sourceMappingURL=utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marktiderman/genesis-ui",
3
- "version": "2.12.0",
3
+ "version": "2.12.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -353,7 +353,7 @@
353
353
  }
354
354
  },
355
355
  "peerDependencies": {
356
- "@marktiderman/genesis-design-system": "^1.3.1",
356
+ "@marktiderman/genesis-design-system": "^1.4.1",
357
357
  "@tanstack/react-router": "^1.0.0",
358
358
  "@tiptap/core": "^3.31.0",
359
359
  "@tiptap/extensions": "^3.31.0",
@@ -378,7 +378,7 @@
378
378
  "vaul": "^1.0.0"
379
379
  },
380
380
  "dependencies": {
381
- "@marktiderman/genesis-core": "^1.6.0",
381
+ "@marktiderman/genesis-core": "^1.7.0",
382
382
  "@tanstack/react-table": "^8.21.3",
383
383
  "@tanstack/react-virtual": "^3.14.0",
384
384
  "class-variance-authority": "^0.7.1",
@@ -387,7 +387,7 @@
387
387
  },
388
388
  "devDependencies": {
389
389
  "@hookform/resolvers": "^5.2.2",
390
- "@marktiderman/genesis-design-system": "^1.3.1",
390
+ "@marktiderman/genesis-design-system": "^1.4.1",
391
391
  "@radix-ui/react-accordion": "^1.2.0",
392
392
  "@radix-ui/react-aspect-ratio": "^1.1.0",
393
393
  "@radix-ui/react-avatar": "^1.1.0",
@@ -1,12 +0,0 @@
1
- import { jsx } from 'react/jsx-runtime';
2
-
3
- // src/slots.tsx
4
- function renderSlot(Default, override, props, extra) {
5
- if (override === null) return null;
6
- const Component = override ?? Default;
7
- return /* @__PURE__ */ jsx(Component, { ...props, ...extra });
8
- }
9
-
10
- export { renderSlot };
11
- //# sourceMappingURL=chunk-7S7LKQBD.js.map
12
- //# sourceMappingURL=chunk-7S7LKQBD.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/slots.tsx"],"names":[],"mappings":";;;AA6DO,SAAS,UAAA,CACd,OAAA,EACA,QAAA,EACA,KAAA,EACA,KAAA,EACW;AACX,EAAA,IAAI,QAAA,KAAa,MAAM,OAAO,IAAA;AAC9B,EAAA,MAAM,YAAY,QAAA,IAAY,OAAA;AAC9B,EAAA,uBAAO,GAAA,CAAC,SAAA,EAAA,EAAW,GAAG,KAAA,EAAQ,GAAI,KAAA,EAAa,CAAA;AACjD","file":"chunk-7S7LKQBD.js","sourcesContent":["import type { ComponentType, ReactNode } from \"react\";\n\n/**\n * The slot contract — genesis's escape hatch from an all-or-nothing pattern.\n *\n * A pattern component (`ResourcePage`, `DataPageShell`, …) is a composition of\n * interior parts. Without a way to reach those parts, a consumer who needs ONE\n * of them to differ has exactly two options: accept the whole thing as shipped,\n * or fork it. Both are bad, and the second is what actually happens — which is\n * how a downstream app ends up maintaining its own copy of a header it only\n * wanted to change one button in.\n *\n * Genesis offers a customisation ladder, and this is its missing rung:\n *\n * 1. the pattern `<ResourcePage title=\"Questions\" />`\n * 2. the pattern + slots ← here: keep the wiring, swap or drop one part\n * 3. the primitives `<DataPageShell><DataTable/></DataPageShell>`\n * 4. the hook `useResourcePage()`, render it all yourself\n *\n * Rungs 1, 3 and 4 already existed (75 subpath exports, 9 hooks). Rung 2 is\n * the one that keeps a consumer ON the ladder: dropping to rung 3 because a\n * single part was wrong means re-deriving all the wiring that was already\n * right, and that re-derivation is where the forks come from.\n *\n * Two knobs, following the `slots`/`slotProps` convention Material UI settled\n * on (mui/material-ui#33416) so the shape is one a React developer already\n * knows rather than one genesis invented:\n *\n * `slots` — REPLACE a part. Your component receives exactly the props\n * genesis would have passed the default, so it can wrap the\n * default and change one thing rather than reimplement it.\n * `slotProps` — KEEP the part, pass it more props. The common case: a\n * `className`, a `data-testid`, an `aria-label`.\n *\n * And the eject button: `slots={{ bulkBar: null }}` drops that part entirely.\n * A pattern is a default, not a mandate — a consumer who wants the shell and\n * the grid but none of the bulk bar should not have to leave to get that.\n */\n\n/** Replace a part (`ComponentType`), or eject it (`null`). */\nexport type SlotOverride<P> = ComponentType<P> | null;\n\n/** `{ [part]: Component | null }` — see {@link renderSlot}. */\nexport type Slots<M extends Record<string, object>> = {\n [K in keyof M]?: SlotOverride<M[K]>;\n};\n\n/** `{ [part]: extraProps }` — merged over what genesis passes. */\nexport type SlotPropsFor<M extends Record<string, object>> = {\n [K in keyof M]?: Partial<M[K]>;\n};\n\n/**\n * Renders one slot: the override if given, the default otherwise, nothing if\n * ejected with `null`.\n *\n * `extra` merges LAST, so `slotProps` wins over genesis's own value. That is\n * deliberate: a consumer reaching for `slotProps` is overriding on purpose,\n * and a hatch that silently loses to the default is not a hatch. `className`\n * is the one prop a caller should merge with `cn()` instead of replacing.\n */\nexport function renderSlot<P extends object>(\n Default: ComponentType<P>,\n override: SlotOverride<P> | undefined,\n props: P,\n extra?: Partial<P>,\n): ReactNode {\n if (override === null) return null;\n const Component = override ?? Default;\n return <Component {...props} {...(extra as P)} />;\n}\n"]}
@@ -1,141 +0,0 @@
1
- "use client";
2
- import { useState, useEffect, useCallback } from 'react';
3
- import { jsx } from 'react/jsx-runtime';
4
-
5
- // src/navigation.tsx
6
- var DefaultLink = ({
7
- href,
8
- children,
9
- testID,
10
- ...rest
11
- }) => /* @__PURE__ */ jsx("a", { href, "data-testid": testID, ...rest, children });
12
- function splitHref(href) {
13
- const hashAt = href.indexOf("#");
14
- const hash = hashAt === -1 ? "" : href.slice(hashAt + 1);
15
- const beforeHash = hashAt === -1 ? href : href.slice(0, hashAt);
16
- const searchAt = beforeHash.indexOf("?");
17
- const rawPath = searchAt === -1 ? beforeHash : beforeHash.slice(0, searchAt);
18
- return {
19
- path: rawPath.length > 1 ? rawPath.replace(/\/+$/, "") || "/" : rawPath,
20
- search: searchAt === -1 ? "" : beforeHash.slice(searchAt + 1),
21
- hash
22
- };
23
- }
24
- function isPathActive(to, activePath, options) {
25
- if (activePath == null) return false;
26
- if (to === activePath) return true;
27
- const target = splitHref(to);
28
- const current = splitHref(activePath);
29
- if (!target.search && !target.hash) {
30
- if (target.path === current.path) return true;
31
- if (options?.end) return false;
32
- return target.path !== "/" && current.path.startsWith(target.path + "/");
33
- }
34
- if (target.path !== current.path) return false;
35
- if (target.hash && target.hash !== current.hash) return false;
36
- if (target.search) {
37
- const want = new URLSearchParams(target.search);
38
- const have = new URLSearchParams(current.search);
39
- for (const key of new Set(want.keys())) {
40
- const wanted = want.getAll(key);
41
- const had = have.getAll(key);
42
- if (!wanted.every((value) => had.includes(value))) return false;
43
- }
44
- }
45
- return true;
46
- }
47
- function pathSegments(path) {
48
- return splitHref(path).path.split("/").filter(Boolean);
49
- }
50
- function decodeSegment(segment) {
51
- try {
52
- return decodeURIComponent(segment);
53
- } catch {
54
- return segment;
55
- }
56
- }
57
- function resolveNavigation(tree, pathname) {
58
- const segments = pathSegments(pathname).map(decodeSegment);
59
- const matches = (pattern) => pattern.length <= segments.length && pattern.every(
60
- (part, i) => part.startsWith("$") || decodeSegment(part) === segments[i]
61
- );
62
- const compare = (a, b) => {
63
- const shared = Math.min(a.pattern.length, b.pattern.length);
64
- for (let i = 0; i < shared; i++) {
65
- const aParam = a.pattern[i].startsWith("$");
66
- const bParam = b.pattern[i].startsWith("$");
67
- if (aParam !== bParam) return aParam ? -1 : 1;
68
- }
69
- if (a.pattern.length !== b.pattern.length) {
70
- return a.pattern.length - b.pattern.length;
71
- }
72
- return a.trail.length - b.trail.length;
73
- };
74
- let best;
75
- const visit = (node, ancestors) => {
76
- const trail2 = [...ancestors, node];
77
- const pattern = pathSegments(node.path);
78
- if (matches(pattern)) {
79
- const candidate = { trail: trail2, pattern };
80
- if (!best || compare(candidate, best) > 0) best = candidate;
81
- }
82
- for (const child of node.children ?? []) visit(child, trail2);
83
- };
84
- visit(tree, []);
85
- const winner = best ?? { trail: [tree], pattern: [] };
86
- const params = {};
87
- winner.pattern.forEach((part, i) => {
88
- if (part.startsWith("$")) params[part.slice(1)] = segments[i];
89
- });
90
- const interpolate = (node) => ({
91
- ...node,
92
- path: node.path.split("/").map((part) => {
93
- if (!part.startsWith("$")) return part;
94
- const value = params[part.slice(1)];
95
- return value == null ? part : encodeURIComponent(value);
96
- }).join("/")
97
- });
98
- const trail = winner.trail.map(interpolate);
99
- const current = trail[trail.length - 1];
100
- return {
101
- current,
102
- parent: trail.length > 1 ? trail[trail.length - 2] : void 0,
103
- items: current.items,
104
- trail,
105
- params
106
- };
107
- }
108
- var defaultNavigate = (path) => {
109
- if (typeof window !== "undefined") window.location.assign(path);
110
- };
111
- function useBrowserSearchParams() {
112
- const read = () => new URLSearchParams(
113
- typeof window !== "undefined" ? window.location.search : ""
114
- );
115
- const [searchParams, setLocal] = useState(read);
116
- useEffect(() => {
117
- if (typeof window === "undefined") return;
118
- const onPop = () => setLocal(read());
119
- window.addEventListener("popstate", onPop);
120
- return () => window.removeEventListener("popstate", onPop);
121
- }, []);
122
- const setSearchParams = useCallback(
123
- (next, options) => {
124
- if (typeof window === "undefined") return;
125
- const qs = next.toString();
126
- const url = window.location.pathname + (qs ? `?${qs}` : "") + window.location.hash;
127
- if (options?.replace) {
128
- window.history.replaceState(window.history.state, "", url);
129
- } else {
130
- window.history.pushState(window.history.state, "", url);
131
- }
132
- setLocal(new URLSearchParams(next));
133
- },
134
- []
135
- );
136
- return { searchParams, setSearchParams };
137
- }
138
-
139
- export { DefaultLink, defaultNavigate, isPathActive, resolveNavigation, useBrowserSearchParams };
140
- //# sourceMappingURL=chunk-KC43CNMA.js.map
141
- //# sourceMappingURL=chunk-KC43CNMA.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/navigation.tsx"],"names":["trail"],"mappings":";;;;AAmEO,IAAM,cAA6B,CAAC;AAAA,EACzC,IAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,GAAG;AACL,CAAA,yBACG,GAAA,EAAA,EAAE,IAAA,EAAY,eAAa,MAAA,EAAS,GAAG,MACrC,QAAA,EACH;AAOF,SAAS,UAAU,IAAA,EAA8D;AAC/E,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAC/B,EAAA,MAAM,OAAO,MAAA,KAAW,EAAA,GAAK,KAAK,IAAA,CAAK,KAAA,CAAM,SAAS,CAAC,CAAA;AACvD,EAAA,MAAM,aAAa,MAAA,KAAW,EAAA,GAAK,OAAO,IAAA,CAAK,KAAA,CAAM,GAAG,MAAM,CAAA;AAC9D,EAAA,MAAM,QAAA,GAAW,UAAA,CAAW,OAAA,CAAQ,GAAG,CAAA;AACvC,EAAA,MAAM,UAAU,QAAA,KAAa,EAAA,GAAK,aAAa,UAAA,CAAW,KAAA,CAAM,GAAG,QAAQ,CAAA;AAC3E,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAQ,MAAA,GAAS,CAAA,GAAI,QAAQ,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA,IAAK,GAAA,GAAM,OAAA;AAAA,IAChE,QAAQ,QAAA,KAAa,EAAA,GAAK,KAAK,UAAA,CAAW,KAAA,CAAM,WAAW,CAAC,CAAA;AAAA,IAC5D;AAAA,GACF;AACF;AA2BO,SAAS,YAAA,CACd,EAAA,EACA,UAAA,EACA,OAAA,EACS;AACT,EAAA,IAAI,UAAA,IAAc,MAAM,OAAO,KAAA;AAC/B,EAAA,IAAI,EAAA,KAAO,YAAY,OAAO,IAAA;AAC9B,EAAA,MAAM,MAAA,GAAS,UAAU,EAAE,CAAA;AAC3B,EAAA,MAAM,OAAA,GAAU,UAAU,UAAU,CAAA;AAEpC,EAAA,IAAI,CAAC,MAAA,CAAO,MAAA,IAAU,CAAC,OAAO,IAAA,EAAM;AAClC,IAAA,IAAI,MAAA,CAAO,IAAA,KAAS,OAAA,CAAQ,IAAA,EAAM,OAAO,IAAA;AACzC,IAAA,IAAI,OAAA,EAAS,KAAK,OAAO,KAAA;AACzB,IAAA,OAAO,MAAA,CAAO,SAAS,GAAA,IAAO,OAAA,CAAQ,KAAK,UAAA,CAAW,MAAA,CAAO,OAAO,GAAG,CAAA;AAAA,EACzE;AAEA,EAAA,IAAI,MAAA,CAAO,IAAA,KAAS,OAAA,CAAQ,IAAA,EAAM,OAAO,KAAA;AACzC,EAAA,IAAI,OAAO,IAAA,IAAQ,MAAA,CAAO,IAAA,KAAS,OAAA,CAAQ,MAAM,OAAO,KAAA;AACxD,EAAA,IAAI,OAAO,MAAA,EAAQ;AACjB,IAAA,MAAM,IAAA,GAAO,IAAI,eAAA,CAAgB,MAAA,CAAO,MAAM,CAAA;AAC9C,IAAA,MAAM,IAAA,GAAO,IAAI,eAAA,CAAgB,OAAA,CAAQ,MAAM,CAAA;AAC/C,IAAA,KAAA,MAAW,OAAO,IAAI,GAAA,CAAI,IAAA,CAAK,IAAA,EAAM,CAAA,EAAG;AACtC,MAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,GAAG,CAAA;AAC9B,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,MAAA,CAAO,GAAG,CAAA;AAC3B,MAAA,IAAI,CAAC,MAAA,CAAO,KAAA,CAAM,CAAC,KAAA,KAAU,IAAI,QAAA,CAAS,KAAK,CAAC,CAAA,EAAG,OAAO,KAAA;AAAA,IAC5D;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAmEA,SAAS,aAAa,IAAA,EAAwB;AAC5C,EAAA,OAAO,SAAA,CAAU,IAAI,CAAA,CAAE,IAAA,CAAK,MAAM,GAAG,CAAA,CAAE,OAAO,OAAO,CAAA;AACvD;AAEA,SAAS,cAAc,OAAA,EAAyB;AAC9C,EAAA,IAAI;AACF,IAAA,OAAO,mBAAmB,OAAO,CAAA;AAAA,EACnC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,OAAA;AAAA,EACT;AACF;AAkBO,SAAS,iBAAA,CACd,MACA,QAAA,EACuB;AAGvB,EAAA,MAAM,QAAA,GAAW,YAAA,CAAa,QAAQ,CAAA,CAAE,IAAI,aAAa,CAAA;AAGzD,EAAA,MAAM,UAAU,CAAC,OAAA,KACf,QAAQ,MAAA,IAAU,QAAA,CAAS,UAC3B,OAAA,CAAQ,KAAA;AAAA,IACN,CAAC,IAAA,EAAM,CAAA,KAAM,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,IAAK,aAAA,CAAc,IAAI,CAAA,KAAM,QAAA,CAAS,CAAC;AAAA,GACzE;AAMF,EAAA,MAAM,OAAA,GAAU,CAAC,CAAA,EAAU,CAAA,KAAqB;AAC9C,IAAA,MAAM,MAAA,GAAS,KAAK,GAAA,CAAI,CAAA,CAAE,QAAQ,MAAA,EAAQ,CAAA,CAAE,QAAQ,MAAM,CAAA;AAC1D,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC/B,MAAA,MAAM,SAAS,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAA,CAAG,WAAW,GAAG,CAAA;AAC3C,MAAA,MAAM,SAAS,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAA,CAAG,WAAW,GAAG,CAAA;AAC3C,MAAA,IAAI,MAAA,KAAW,MAAA,EAAQ,OAAO,MAAA,GAAS,EAAA,GAAK,CAAA;AAAA,IAC9C;AACA,IAAA,IAAI,CAAA,CAAE,OAAA,CAAQ,MAAA,KAAW,CAAA,CAAE,QAAQ,MAAA,EAAQ;AACzC,MAAA,OAAO,CAAA,CAAE,OAAA,CAAQ,MAAA,GAAS,CAAA,CAAE,OAAA,CAAQ,MAAA;AAAA,IACtC;AACA,IAAA,OAAO,CAAA,CAAE,KAAA,CAAM,MAAA,GAAS,CAAA,CAAE,KAAA,CAAM,MAAA;AAAA,EAClC,CAAA;AAEA,EAAA,IAAI,IAAA;AACJ,EAAA,MAAM,KAAA,GAAQ,CAAC,IAAA,EAAyB,SAAA,KAAmC;AACzE,IAAA,MAAMA,MAAAA,GAAQ,CAAC,GAAG,SAAA,EAAW,IAAI,CAAA;AACjC,IAAA,MAAM,OAAA,GAAU,YAAA,CAAa,IAAA,CAAK,IAAI,CAAA;AACtC,IAAA,IAAI,OAAA,CAAQ,OAAO,CAAA,EAAG;AACpB,MAAA,MAAM,SAAA,GAAY,EAAE,KAAA,EAAAA,MAAAA,EAAO,OAAA,EAAQ;AACnC,MAAA,IAAI,CAAC,IAAA,IAAQ,OAAA,CAAQ,WAAW,IAAI,CAAA,GAAI,GAAG,IAAA,GAAO,SAAA;AAAA,IACpD;AACA,IAAA,KAAA,MAAW,SAAS,IAAA,CAAK,QAAA,IAAY,EAAC,EAAG,KAAA,CAAM,OAAOA,MAAK,CAAA;AAAA,EAC7D,CAAA;AACA,EAAA,KAAA,CAAM,IAAA,EAAM,EAAE,CAAA;AAEd,EAAA,MAAM,MAAA,GAAgB,QAAQ,EAAE,KAAA,EAAO,CAAC,IAAI,CAAA,EAAG,OAAA,EAAS,EAAC,EAAE;AAC3D,EAAA,MAAM,SAAiC,EAAC;AACxC,EAAA,MAAA,CAAO,OAAA,CAAQ,OAAA,CAAQ,CAAC,IAAA,EAAM,CAAA,KAAM;AAClC,IAAA,IAAI,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA,GAAI,QAAA,CAAS,CAAC,CAAA;AAAA,EAC9D,CAAC,CAAA;AAED,EAAA,MAAM,WAAA,GAAc,CAAC,IAAA,MAAgD;AAAA,IACnE,GAAG,IAAA;AAAA,IACH,IAAA,EAAM,KAAK,IAAA,CACR,KAAA,CAAM,GAAG,CAAA,CACT,GAAA,CAAI,CAAC,IAAA,KAAS;AACb,MAAA,IAAI,CAAC,IAAA,CAAK,UAAA,CAAW,GAAG,GAAG,OAAO,IAAA;AAClC,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AAClC,MAAA,OAAO,KAAA,IAAS,IAAA,GAAO,IAAA,GAAO,kBAAA,CAAmB,KAAK,CAAA;AAAA,IACxD,CAAC,CAAA,CACA,IAAA,CAAK,GAAG;AAAA,GACb,CAAA;AAEA,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,GAAA,CAAI,WAAW,CAAA;AAC1C,EAAA,MAAM,OAAA,GAAU,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA;AACtC,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,MAAA,EAAQ,MAAM,MAAA,GAAS,CAAA,GAAI,MAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,GAAI,MAAA;AAAA,IACrD,OAAO,OAAA,CAAQ,KAAA;AAAA,IACf,KAAA;AAAA,IACA;AAAA,GACF;AACF;AAkBO,IAAM,eAAA,GAA8B,CAAC,IAAA,KAAS;AACnD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,MAAA,CAAO,QAAA,CAAS,OAAO,IAAI,CAAA;AAChE;AA4BO,SAAS,sBAAA,GAA8C;AAC5D,EAAA,MAAM,IAAA,GAAO,MACX,IAAI,eAAA;AAAA,IACF,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,SAAS,MAAA,GAAS;AAAA,GAC3D;AAEF,EAAA,MAAM,CAAC,YAAA,EAAc,QAAQ,CAAA,GAAI,SAA0B,IAAI,CAAA;AAE/D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,IAAA,MAAM,KAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAM,CAAA;AACnC,IAAA,MAAA,CAAO,gBAAA,CAAiB,YAAY,KAAK,CAAA;AACzC,IAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,UAAA,EAAY,KAAK,CAAA;AAAA,EAC3D,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,eAAA,GAAkB,WAAA;AAAA,IACtB,CAAC,MAAM,OAAA,KAAY;AACjB,MAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,MAAA,MAAM,EAAA,GAAK,KAAK,QAAA,EAAS;AACzB,MAAA,MAAM,GAAA,GACJ,MAAA,CAAO,QAAA,CAAS,QAAA,IAAY,EAAA,GAAK,IAAI,EAAE,CAAA,CAAA,GAAK,EAAA,CAAA,GAAM,MAAA,CAAO,QAAA,CAAS,IAAA;AACpE,MAAA,IAAI,SAAS,OAAA,EAAS;AACpB,QAAA,MAAA,CAAO,QAAQ,YAAA,CAAa,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,IAAI,GAAG,CAAA;AAAA,MAC3D,CAAA,MAAO;AACL,QAAA,MAAA,CAAO,QAAQ,SAAA,CAAU,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,IAAI,GAAG,CAAA;AAAA,MACxD;AACA,MAAA,QAAA,CAAS,IAAI,eAAA,CAAgB,IAAI,CAAC,CAAA;AAAA,IACpC,CAAA;AAAA,IACA;AAAC,GACH;AAEA,EAAA,OAAO,EAAE,cAAc,eAAA,EAAgB;AACzC","file":"chunk-KC43CNMA.js","sourcesContent":["\"use client\";\n\n/**\n * Router-agnostic navigation primitives.\n *\n * genesis-ui does NOT depend on any router. Components that need to render a\n * link or navigate imperatively accept an injectable abstraction defined here,\n * and default to plain browser behavior (`<a>` / `window.location`). Consumers\n * on a router inject an adapter that maps these props to their own `<Link>` /\n * router. Ready-made ones ship on their own subpaths, each with its router as\n * an optional peer: `@marktiderman/genesis-ui/router/react-router` and\n * `@marktiderman/genesis-ui/router/tanstack-router`. Next.js passes\n * `next/link` directly.\n */\nimport {\n useCallback,\n useEffect,\n useState,\n type AnchorHTMLAttributes,\n type ComponentType,\n type ReactNode,\n} from \"react\";\n\n// ---------------------------------------------------------------------------\n// Link abstraction\n// ---------------------------------------------------------------------------\n\n/**\n * Props passed to an injectable link component. The contract is a plain anchor\n * (`href`), so the default works with NO router. A React Router consumer maps\n * `href` → `to`; a Next.js consumer passes `next/link` directly (it already\n * accepts `href`, `className`, `children`).\n *\n * @stability Beta\n */\nexport interface GenesisLinkProps {\n /** Destination path or URL. */\n href: string;\n className?: string;\n onClick?: () => void;\n children?: ReactNode;\n /** Set to \"page\" when the link points at the active route. */\n \"aria-current\"?: AnchorHTMLAttributes<HTMLAnchorElement>[\"aria-current\"];\n /** Accessible name — useful when the visible label is hidden (icon rail). */\n \"aria-label\"?: string;\n /** Native tooltip / title attribute. */\n title?: string;\n /**\n * Stable test/automation selector. {@link DefaultLink} renders it as\n * `data-testid`; injected router adapters (`next/link`, RR `Link`) forward it\n * to the underlying `<a>` too.\n */\n testID?: string;\n}\n\n/**\n * A component that renders a navigable link from {@link GenesisLinkProps}.\n *\n * @stability Beta\n */\nexport type LinkComponent = ComponentType<GenesisLinkProps>;\n\n/**\n * Default link — a plain `<a>`. No router required.\n *\n * @stability Beta\n */\nexport const DefaultLink: LinkComponent = ({\n href,\n children,\n testID,\n ...rest\n}) => (\n <a href={href} data-testid={testID} {...rest}>\n {children}\n </a>\n);\n\n/**\n * Split a href into its path (trailing slash dropped, except for `/`), query\n * (no `?`) and hash (no `#`).\n */\nfunction splitHref(href: string): { path: string; search: string; hash: string } {\n const hashAt = href.indexOf(\"#\");\n const hash = hashAt === -1 ? \"\" : href.slice(hashAt + 1);\n const beforeHash = hashAt === -1 ? href : href.slice(0, hashAt);\n const searchAt = beforeHash.indexOf(\"?\");\n const rawPath = searchAt === -1 ? beforeHash : beforeHash.slice(0, searchAt);\n return {\n path: rawPath.length > 1 ? rawPath.replace(/\\/+$/, \"\") || \"/\" : rawPath,\n search: searchAt === -1 ? \"\" : beforeHash.slice(searchAt + 1),\n hash,\n };\n}\n\n/**\n * Options for {@link isPathActive}.\n *\n * @stability Beta\n */\nexport interface IsPathActiveOptions {\n /** Require an exact path match instead of a prefix match (like `NavLink end`). */\n end?: boolean;\n}\n\n/**\n * Default active-path matcher, mirroring React Router's `NavLink` (prefix\n * match, with `/` treated as exact). Consumers can override via an `isActive`\n * prop where components accept one.\n *\n * `activePath` may carry a query and hash (`pathname + search + hash`). An\n * item whose `to` has neither compares against the path part only, exactly as\n * before. An item that opts in by putting a query or hash in its `to`\n * (`/admin?tab=users`, `/settings#profile`) is active only when the path\n * matches exactly AND every query param it names has the same value in\n * `activePath` (extra params such as `&page=2` are ignored) AND, if it names a\n * hash, the hash is equal.\n *\n * @stability Beta\n */\nexport function isPathActive(\n to: string,\n activePath?: string,\n options?: IsPathActiveOptions,\n): boolean {\n if (activePath == null) return false;\n if (to === activePath) return true;\n const target = splitHref(to);\n const current = splitHref(activePath);\n\n if (!target.search && !target.hash) {\n if (target.path === current.path) return true;\n if (options?.end) return false;\n return target.path !== \"/\" && current.path.startsWith(target.path + \"/\");\n }\n\n if (target.path !== current.path) return false;\n if (target.hash && target.hash !== current.hash) return false;\n if (target.search) {\n const want = new URLSearchParams(target.search);\n const have = new URLSearchParams(current.search);\n for (const key of new Set(want.keys())) {\n const wanted = want.getAll(key);\n const had = have.getAll(key);\n if (!wanted.every((value) => had.includes(value))) return false;\n }\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Hierarchical navigation resolver\n// ---------------------------------------------------------------------------\n\n/**\n * One node of a logical navigation hierarchy. `path` is a route pattern whose\n * segments are either static (`clients`) or named params (`$clientId`). A\n * node's place in the tree is its LOGICAL parent, which need not share its URL\n * prefix — `/clients/$clientId` can sit under `/dashboard`.\n *\n * @stability Beta\n */\nexport interface NavigationNode<T> {\n id: string;\n path: string;\n label: string;\n /** Contextual nav items to show while this node is current. */\n items: T[];\n children?: NavigationNode<T>[];\n /**\n * Ask the app shell to open its desktop sidebar collapsed (icon rail) while\n * this node, or a descendant that doesn't set its own value, is current.\n * The nearest node in the resolved trail that sets it wins, so\n * `defaultCollapsed: false` on a child re-opens it under a collapsed\n * parent. Only a default: once the user has toggled the sidebar, their\n * choice wins everywhere. `AppShell` reads it; `resolveNavigation` copies\n * it through untouched.\n *\n * @stability Beta\n */\n defaultCollapsed?: boolean;\n /**\n * How the app shell shows this node's `items` (its child pages, e.g. a\n * form's Questions / Logic / Responses / Settings). `\"tabs\"` (across the\n * top of the content) or `\"sidebar\"` (nested under the active item) turn\n * shell sub-navigation on, which also gives phones a back item plus the\n * children in the bottom bar. `\"section\"` is the legacy rendering (the\n * shell's default) and `\"off\"` hides them. The nearest node in the trail\n * that sets it wins over the shell's `options.subNav`.\n *\n * @stability Beta\n */\n subNav?: \"section\" | \"tabs\" | \"sidebar\" | \"off\";\n}\n\n/**\n * Result of {@link resolveNavigation}. `current`, `parent` and every `trail`\n * entry are shallow copies of their tree nodes with `path` interpolated from\n * `params` (a param the URL did not supply stays as `$name`).\n *\n * @stability Beta\n */\nexport interface ResolvedNavigation<T> {\n /** The best-matching node — the root when nothing else matches. */\n current: NavigationNode<T>;\n /** `current`'s logical parent, for an \"up\" / back-to-parent link. */\n parent?: NavigationNode<T>;\n /** `current.items`. */\n items: T[];\n /** Root → `current`, inclusive. */\n trail: NavigationNode<T>[];\n /** Params captured by `current.path`, URL-decoded. */\n params: Record<string, string>;\n}\n\nfunction pathSegments(path: string): string[] {\n return splitHref(path).path.split(\"/\").filter(Boolean);\n}\n\nfunction decodeSegment(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n\n/**\n * Resolve where `pathname` sits in a logical navigation tree. Pure: it never\n * reads browser history or `window`.\n *\n * A node matches when its pattern matches a leading run of WHOLE path\n * segments (`/my` matches `/my/tasks`, never `/my-other`). Among matches the\n * winner is decided by positional specificity: at the first position where\n * one pattern is static and the other a `$param`, the static one wins (so\n * `/clients/new` beats `/clients/$clientId` whatever the sibling order, and\n * `/my` beats `/$space/tasks` for `/my/tasks`). Otherwise the pattern matching\n * more segments wins, then the deeper tree node.\n * Non-matching nodes are still searched, so a match keeps its logical\n * ancestors even when their URLs differ.\n *\n * @stability Beta\n */\nexport function resolveNavigation<T>(\n tree: NavigationNode<T>,\n pathname: string,\n): ResolvedNavigation<T> {\n // Compare decoded forms on both sides, so `/caf%C3%A9` matches a `/café`\n // node (and an encoded pattern matches a decoded pathname).\n const segments = pathSegments(pathname).map(decodeSegment);\n\n type Match = { trail: NavigationNode<T>[]; pattern: string[] };\n const matches = (pattern: string[]) =>\n pattern.length <= segments.length &&\n pattern.every(\n (part, i) => part.startsWith(\"$\") || decodeSegment(part) === segments[i],\n );\n\n // > 0 when `a` should win over `b`.\n // Positional specificity, as TanStack Router and React Router rank routes:\n // walking left to right, the first position where one pattern is static and\n // the other a `$param` decides. Only then does the longer match win.\n const compare = (a: Match, b: Match): number => {\n const shared = Math.min(a.pattern.length, b.pattern.length);\n for (let i = 0; i < shared; i++) {\n const aParam = a.pattern[i]!.startsWith(\"$\");\n const bParam = b.pattern[i]!.startsWith(\"$\");\n if (aParam !== bParam) return aParam ? -1 : 1;\n }\n if (a.pattern.length !== b.pattern.length) {\n return a.pattern.length - b.pattern.length;\n }\n return a.trail.length - b.trail.length;\n };\n\n let best: Match | undefined;\n const visit = (node: NavigationNode<T>, ancestors: NavigationNode<T>[]) => {\n const trail = [...ancestors, node];\n const pattern = pathSegments(node.path);\n if (matches(pattern)) {\n const candidate = { trail, pattern };\n if (!best || compare(candidate, best) > 0) best = candidate;\n }\n for (const child of node.children ?? []) visit(child, trail);\n };\n visit(tree, []);\n\n const winner: Match = best ?? { trail: [tree], pattern: [] };\n const params: Record<string, string> = {};\n winner.pattern.forEach((part, i) => {\n if (part.startsWith(\"$\")) params[part.slice(1)] = segments[i]!;\n });\n\n const interpolate = (node: NavigationNode<T>): NavigationNode<T> => ({\n ...node,\n path: node.path\n .split(\"/\")\n .map((part) => {\n if (!part.startsWith(\"$\")) return part;\n const value = params[part.slice(1)];\n return value == null ? part : encodeURIComponent(value);\n })\n .join(\"/\"),\n });\n\n const trail = winner.trail.map(interpolate);\n const current = trail[trail.length - 1]!;\n return {\n current,\n parent: trail.length > 1 ? trail[trail.length - 2] : undefined,\n items: current.items,\n trail,\n params,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Imperative navigation abstraction\n// ---------------------------------------------------------------------------\n\n/**\n * Injectable imperative navigation callback.\n *\n * @stability Beta\n */\nexport type NavigateFn = (path: string) => void;\n\n/**\n * Default navigate — a full-page navigation. No router required.\n *\n * @stability Beta\n */\nexport const defaultNavigate: NavigateFn = (path) => {\n if (typeof window !== \"undefined\") window.location.assign(path);\n};\n\n// ---------------------------------------------------------------------------\n// Search-params abstraction (for URL-synced hooks)\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal, router-agnostic replacement for React Router's `useSearchParams`\n * return shape. Consumers may inject their router's implementation for full\n * SPA integration; otherwise the browser History API is used.\n *\n * @stability Beta\n */\nexport interface SearchParamsAdapter {\n searchParams: URLSearchParams;\n setSearchParams: (\n next: URLSearchParams,\n options?: { replace?: boolean },\n ) => void;\n}\n\n/**\n * Browser-native {@link SearchParamsAdapter} built on the History API. Reads\n * the current query string and writes updates via `pushState`/`replaceState`,\n * staying reactive to `popstate` events. No router required.\n *\n * @stability Beta\n */\nexport function useBrowserSearchParams(): SearchParamsAdapter {\n const read = () =>\n new URLSearchParams(\n typeof window !== \"undefined\" ? window.location.search : \"\",\n );\n\n const [searchParams, setLocal] = useState<URLSearchParams>(read);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const onPop = () => setLocal(read());\n window.addEventListener(\"popstate\", onPop);\n return () => window.removeEventListener(\"popstate\", onPop);\n }, []);\n\n const setSearchParams = useCallback<SearchParamsAdapter[\"setSearchParams\"]>(\n (next, options) => {\n if (typeof window === \"undefined\") return;\n const qs = next.toString();\n const url =\n window.location.pathname + (qs ? `?${qs}` : \"\") + window.location.hash;\n if (options?.replace) {\n window.history.replaceState(window.history.state, \"\", url);\n } else {\n window.history.pushState(window.history.state, \"\", url);\n }\n setLocal(new URLSearchParams(next));\n },\n [],\n );\n\n return { searchParams, setSearchParams };\n}\n"]}