@burojs/ui-shadcn 0.6.0 → 0.7.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 (152) hide show
  1. package/README.md +9 -0
  2. package/dist/bulk-summary-BWSAS7k2.js +143 -0
  3. package/dist/bulk-summary-BWSAS7k2.js.map +1 -0
  4. package/dist/bulk-summary-CrEij5vq.cjs +143 -0
  5. package/dist/bulk-summary-CrEij5vq.cjs.map +1 -0
  6. package/dist/{candidates-relation-picker-B116Bmqi.js → candidates-relation-picker-CS_JZ4Fd.js} +3 -3
  7. package/dist/{candidates-relation-picker-B116Bmqi.js.map → candidates-relation-picker-CS_JZ4Fd.js.map} +1 -1
  8. package/dist/{candidates-relation-picker-Cy0rTxvI.cjs → candidates-relation-picker-y5M6_oe4.cjs} +4 -4
  9. package/dist/{candidates-relation-picker-Cy0rTxvI.cjs.map → candidates-relation-picker-y5M6_oe4.cjs.map} +1 -1
  10. package/dist/{check-confirm-dialog-bRT8lt0d.js → check-confirm-dialog-2j0gOd7x.js} +384 -108
  11. package/dist/check-confirm-dialog-2j0gOd7x.js.map +1 -0
  12. package/dist/{check-confirm-dialog--gPFnEeg.cjs → check-confirm-dialog-C-NHgMST.cjs} +468 -107
  13. package/dist/check-confirm-dialog-C-NHgMST.cjs.map +1 -0
  14. package/dist/{dialog-B_rt9S1c.js → dialog-C123sVQ8.js} +4 -4
  15. package/dist/dialog-C123sVQ8.js.map +1 -0
  16. package/dist/{dialog-DRNUq9EA.cjs → dialog-vmMdfUWs.cjs} +3 -3
  17. package/dist/dialog-vmMdfUWs.cjs.map +1 -0
  18. package/dist/{filter-builder-nXqtM_h4.cjs → filter-builder-DFu0JP-1.cjs} +923 -120
  19. package/dist/filter-builder-DFu0JP-1.cjs.map +1 -0
  20. package/dist/{filter-builder-Bhn-dhZV.js → filter-builder-DaWHUZuV.js} +839 -108
  21. package/dist/filter-builder-DaWHUZuV.js.map +1 -0
  22. package/dist/{flow-runtime-D3avVH0I.cjs → flow-runtime-BpmrIUb6.cjs} +61 -29
  23. package/dist/flow-runtime-BpmrIUb6.cjs.map +1 -0
  24. package/dist/{flow-runtime-s6lkRPKF.js → flow-runtime-DyqTmK3H.js} +62 -30
  25. package/dist/flow-runtime-DyqTmK3H.js.map +1 -0
  26. package/dist/{flow-screen-Ci_Sik4Y.cjs → flow-screen-Bcz9NF2V.cjs} +128 -87
  27. package/dist/flow-screen-Bcz9NF2V.cjs.map +1 -0
  28. package/dist/{flow-screen-CCvgDI9G.js → flow-screen-DCdli6Ha.js} +128 -87
  29. package/dist/flow-screen-DCdli6Ha.js.map +1 -0
  30. package/dist/index.cjs +731 -906
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +455 -247
  33. package/dist/index.d.ts +455 -247
  34. package/dist/index.js +647 -828
  35. package/dist/index.js.map +1 -1
  36. package/dist/{inline-flow-host-DV1et3c6.cjs → inline-flow-host-3vw7LzzD.cjs} +5 -5
  37. package/dist/inline-flow-host-3vw7LzzD.cjs.map +1 -0
  38. package/dist/{inline-flow-host-B6FI0ctQ.js → inline-flow-host-C0rI9B7f.js} +5 -5
  39. package/dist/inline-flow-host-C0rI9B7f.js.map +1 -0
  40. package/dist/{candidates-gate-D33jXLHO.cjs → lazy-relation-picker-dialog-Bt8lrEIu.cjs} +10 -10
  41. package/dist/lazy-relation-picker-dialog-Bt8lrEIu.cjs.map +1 -0
  42. package/dist/{candidates-gate-Bb70LPAy.js → lazy-relation-picker-dialog-CAScLPb2.js} +11 -11
  43. package/dist/lazy-relation-picker-dialog-CAScLPb2.js.map +1 -0
  44. package/dist/{page-flow-route-i3ZfJ1sd.js → page-flow-route-BHUEpn0I.js} +36 -23
  45. package/dist/page-flow-route-BHUEpn0I.js.map +1 -0
  46. package/dist/{page-flow-route-hdnULWqd.cjs → page-flow-route-DS8IVTVH.cjs} +41 -28
  47. package/dist/page-flow-route-DS8IVTVH.cjs.map +1 -0
  48. package/dist/{place-node-C5SmIAYU.cjs → place-node-CNb3w-c7.cjs} +1869 -947
  49. package/dist/place-node-CNb3w-c7.cjs.map +1 -0
  50. package/dist/{place-node-NraNYKPx.js → place-node-CfzQvh4Y.js} +1824 -926
  51. package/dist/place-node-CfzQvh4Y.js.map +1 -0
  52. package/dist/{related-list-cards-BWZ9GfOG.js → related-list-cards-BIqqY69Y.js} +9 -4
  53. package/dist/{related-list-cards-BWZ9GfOG.js.map → related-list-cards-BIqqY69Y.js.map} +1 -1
  54. package/dist/{related-list-cards-CFq3meUn.cjs → related-list-cards-CelMxl4q.cjs} +9 -4
  55. package/dist/{related-list-cards-CFq3meUn.cjs.map → related-list-cards-CelMxl4q.cjs.map} +1 -1
  56. package/dist/{related-list-timeline-yrPsjA3F.js → related-list-timeline-CRh7xwLD.js} +6 -7
  57. package/dist/related-list-timeline-CRh7xwLD.js.map +1 -0
  58. package/dist/{related-list-timeline-CCzl71aA.cjs → related-list-timeline-DsLdi8sv.cjs} +5 -6
  59. package/dist/related-list-timeline-DsLdi8sv.cjs.map +1 -0
  60. package/dist/{related-rows-o0_t-Yce.js → related-rows--uP2iKQC.js} +25 -14
  61. package/dist/related-rows--uP2iKQC.js.map +1 -0
  62. package/dist/{related-rows-ea43qhhy.cjs → related-rows-MPnLCmPF.cjs} +27 -16
  63. package/dist/related-rows-MPnLCmPF.cjs.map +1 -0
  64. package/dist/{search-access-CPPmfuvy.js → relation-create-C43cdPQt.js} +595 -13
  65. package/dist/relation-create-C43cdPQt.js.map +1 -0
  66. package/dist/{search-access-Dugvttlg.cjs → relation-create-DDEYcKfw.cjs} +633 -14
  67. package/dist/relation-create-DDEYcKfw.cjs.map +1 -0
  68. package/dist/{relation-many-picker-bvkjkv-k.js → relation-many-picker-BJhaEqxz.js} +203 -102
  69. package/dist/relation-many-picker-BJhaEqxz.js.map +1 -0
  70. package/dist/{relation-many-picker-C8zFEjwd.cjs → relation-many-picker-DzuGJpEl.cjs} +211 -110
  71. package/dist/relation-many-picker-DzuGJpEl.cjs.map +1 -0
  72. package/dist/{relation-picker-Z0ZJJ5uS.js → relation-picker-D-tBBZW_.js} +23 -59
  73. package/dist/relation-picker-D-tBBZW_.js.map +1 -0
  74. package/dist/{relation-picker-gag0qSR5.cjs → relation-picker-DaIMzf2H.cjs} +29 -71
  75. package/dist/relation-picker-DaIMzf2H.cjs.map +1 -0
  76. package/dist/{relation-picker-dialog-0IzkjmiA.cjs → relation-picker-dialog-9Vg5vvR1.cjs} +133 -35
  77. package/dist/relation-picker-dialog-9Vg5vvR1.cjs.map +1 -0
  78. package/dist/{relation-picker-dialog-B6nIiJBf.js → relation-picker-dialog-DoEY8EkH.js} +134 -36
  79. package/dist/relation-picker-dialog-DoEY8EkH.js.map +1 -0
  80. package/dist/relation-select-B8Fq4o-t.js +48 -0
  81. package/dist/relation-select-B8Fq4o-t.js.map +1 -0
  82. package/dist/relation-select-Cr3CfNLy.cjs +55 -0
  83. package/dist/relation-select-Cr3CfNLy.cjs.map +1 -0
  84. package/dist/{resource-page-router-PXfMW9nh.cjs → resource-page-router-C_kGW2o_.cjs} +543 -300
  85. package/dist/resource-page-router-C_kGW2o_.cjs.map +1 -0
  86. package/dist/{resource-page-router-3RUf9d1j.js → resource-page-router-Diw6S6TK.js} +498 -255
  87. package/dist/resource-page-router-Diw6S6TK.js.map +1 -0
  88. package/dist/{rich-text-vTyVjDoa.cjs → rich-text-C_7JCvRo.cjs} +2 -2
  89. package/dist/{rich-text-vTyVjDoa.cjs.map → rich-text-C_7JCvRo.cjs.map} +1 -1
  90. package/dist/{rich-text-DFerywgz.js → rich-text-RcPBf6hr.js} +2 -2
  91. package/dist/{rich-text-DFerywgz.js.map → rich-text-RcPBf6hr.js.map} +1 -1
  92. package/dist/success-notices-Dl3M1fZP.cjs +46 -0
  93. package/dist/success-notices-Dl3M1fZP.cjs.map +1 -0
  94. package/dist/success-notices-LDEgEkc4.js +41 -0
  95. package/dist/success-notices-LDEgEkc4.js.map +1 -0
  96. package/dist/{toaster-Cr51X7bz.cjs → toaster-BbByOytP.cjs} +60 -15
  97. package/dist/toaster-BbByOytP.cjs.map +1 -0
  98. package/dist/{toaster-C02XbB9r.js → toaster-Ct_YHGs6.js} +43 -16
  99. package/dist/toaster-Ct_YHGs6.js.map +1 -0
  100. package/dist/{use-dialog-filter-state-C--oMAZG.js → use-dialog-filter-state-B_MqzHaT.js} +5 -4
  101. package/dist/use-dialog-filter-state-B_MqzHaT.js.map +1 -0
  102. package/dist/{use-dialog-filter-state-Dhe8PcB5.cjs → use-dialog-filter-state-tNvqAAjh.cjs} +6 -5
  103. package/dist/use-dialog-filter-state-tNvqAAjh.cjs.map +1 -0
  104. package/package.json +11 -11
  105. package/dist/bulk-summary-CwSd6Yjb.cjs +0 -108
  106. package/dist/bulk-summary-CwSd6Yjb.cjs.map +0 -1
  107. package/dist/bulk-summary-GxMDP4bw.js +0 -108
  108. package/dist/bulk-summary-GxMDP4bw.js.map +0 -1
  109. package/dist/candidates-gate-Bb70LPAy.js.map +0 -1
  110. package/dist/candidates-gate-D33jXLHO.cjs.map +0 -1
  111. package/dist/check-confirm-dialog--gPFnEeg.cjs.map +0 -1
  112. package/dist/check-confirm-dialog-bRT8lt0d.js.map +0 -1
  113. package/dist/dialog-B_rt9S1c.js.map +0 -1
  114. package/dist/dialog-DRNUq9EA.cjs.map +0 -1
  115. package/dist/filter-builder-Bhn-dhZV.js.map +0 -1
  116. package/dist/filter-builder-nXqtM_h4.cjs.map +0 -1
  117. package/dist/flow-runtime-D3avVH0I.cjs.map +0 -1
  118. package/dist/flow-runtime-s6lkRPKF.js.map +0 -1
  119. package/dist/flow-screen-CCvgDI9G.js.map +0 -1
  120. package/dist/flow-screen-Ci_Sik4Y.cjs.map +0 -1
  121. package/dist/inline-flow-host-B6FI0ctQ.js.map +0 -1
  122. package/dist/inline-flow-host-DV1et3c6.cjs.map +0 -1
  123. package/dist/page-flow-route-hdnULWqd.cjs.map +0 -1
  124. package/dist/page-flow-route-i3ZfJ1sd.js.map +0 -1
  125. package/dist/place-node-C5SmIAYU.cjs.map +0 -1
  126. package/dist/place-node-NraNYKPx.js.map +0 -1
  127. package/dist/related-list-timeline-CCzl71aA.cjs.map +0 -1
  128. package/dist/related-list-timeline-yrPsjA3F.js.map +0 -1
  129. package/dist/related-rows-ea43qhhy.cjs.map +0 -1
  130. package/dist/related-rows-o0_t-Yce.js.map +0 -1
  131. package/dist/relation-create-IA40t1hi.cjs +0 -364
  132. package/dist/relation-create-IA40t1hi.cjs.map +0 -1
  133. package/dist/relation-create-h6F9B4C4.js +0 -339
  134. package/dist/relation-create-h6F9B4C4.js.map +0 -1
  135. package/dist/relation-many-picker-C8zFEjwd.cjs.map +0 -1
  136. package/dist/relation-many-picker-bvkjkv-k.js.map +0 -1
  137. package/dist/relation-picker-Z0ZJJ5uS.js.map +0 -1
  138. package/dist/relation-picker-dialog-0IzkjmiA.cjs.map +0 -1
  139. package/dist/relation-picker-dialog-B6nIiJBf.js.map +0 -1
  140. package/dist/relation-picker-gag0qSR5.cjs.map +0 -1
  141. package/dist/resource-page-router-3RUf9d1j.js.map +0 -1
  142. package/dist/resource-page-router-PXfMW9nh.cjs.map +0 -1
  143. package/dist/search-access-CPPmfuvy.js.map +0 -1
  144. package/dist/search-access-Dugvttlg.cjs.map +0 -1
  145. package/dist/shell-BPlk8hEa.js +0 -205
  146. package/dist/shell-BPlk8hEa.js.map +0 -1
  147. package/dist/shell-DxhlsPwm.cjs +0 -271
  148. package/dist/shell-DxhlsPwm.cjs.map +0 -1
  149. package/dist/toaster-C02XbB9r.js.map +0 -1
  150. package/dist/toaster-Cr51X7bz.cjs.map +0 -1
  151. package/dist/use-dialog-filter-state-C--oMAZG.js.map +0 -1
  152. package/dist/use-dialog-filter-state-Dhe8PcB5.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toaster-BbByOytP.cjs","names":["PopoverPrimitive","React","cn","forwardRef","createContext","useContext","toast","useOptionalT","useTheme","useRef","useMemo","SonnerToaster"],"sources":["../src/primitives/popover.tsx","../src/layout/link-component.tsx","../src/lib/friendly-error.ts","../src/notification/notification-provider.ts","../src/notification/toaster.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\nimport { cn } from '../lib/cn.js';\n\n/**\n * Thin wrapper around Radix Popover, mirroring the structure of the\n * Dialog / Select primitives in this package so the Tailwind tokens\n * stay aligned. Re-exports `Root` / `Trigger` / `Anchor` / `Close`\n * directly (no styling needed) and styles `Content` to match the\n * shadcn Popover defaults.\n */\n\nconst Popover = PopoverPrimitive.Root;\nconst PopoverTrigger = PopoverPrimitive.Trigger;\nconst PopoverAnchor = PopoverPrimitive.Anchor;\nconst PopoverClose = PopoverPrimitive.Close;\n\nexport type PopoverContentProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>;\n\nconst PopoverContent = React.forwardRef<\n React.ElementRef<typeof PopoverPrimitive.Content>,\n PopoverContentProps\n>(({ className, align = 'start', sideOffset = 4, ...props }, ref) => (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n className={cn(\n 'z-50 w-72 rounded-md border bg-popover p-3 text-popover-foreground shadow-md outline-hidden',\n 'duration-[var(--duration-overlay)] ease-[var(--ease-overlay)]',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n className,\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n));\nPopoverContent.displayName = PopoverPrimitive.Content.displayName;\n\nexport { Popover, PopoverTrigger, PopoverAnchor, PopoverClose, PopoverContent };\n","'use client';\nimport {\n type AnchorHTMLAttributes,\n type ComponentType,\n type ReactElement,\n type ReactNode,\n createContext,\n forwardRef,\n useContext,\n} from 'react';\n\n/**\n * Props every `LinkComponent` must accept. We intentionally mirror the\n * native `<a>` API surface so adopters writing a small wrapper around\n * their router's link primitive (next/link, react-router, TanStack\n * Router, etc.) can pass them straight through.\n *\n * `next/link` already accepts these names verbatim. `react-router`'s\n * `<Link>` uses `to` instead of `href` — adopters wrap once:\n *\n * const RouterLink: LinkComponent = ({ href, ...rest }) =>\n * <Link to={href} {...rest} />;\n */\nexport type LinkComponentProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> & {\n href: string;\n children?: ReactNode;\n};\n\nexport type LinkComponent = ComponentType<LinkComponentProps>;\n\n/**\n * Default fallback — a plain `<a href>` anchor. Triggers a full\n * document load. Adopters that ship a SPA router (Next.js App Router,\n * react-router) install their router's `<Link>` via\n * `<LinkComponentProvider>` so internal navigation stays client-side.\n */\nconst DefaultLinkComponent: LinkComponent = forwardRef<HTMLAnchorElement, LinkComponentProps>(\n function DefaultLinkComponent({ children, ...rest }, ref) {\n return (\n <a ref={ref} {...rest}>\n {children}\n </a>\n );\n },\n);\n\nconst LinkComponentContext = createContext<LinkComponent>(DefaultLinkComponent);\n\nexport interface LinkComponentProviderProps {\n /**\n * The router-aware Link component to use throughout `@burojs/ui-shadcn` for\n * internal navigation. Pass `Link` from `next/link` for Next.js App Router;\n * wrap your router's link primitive once for react-router / TanStack Router.\n */\n linkComponent: LinkComponent;\n children: ReactNode;\n}\n\n/**\n * Install a router-aware `LinkComponent` for every framework component that\n * renders an internal navigation link (sidebar entries, action links, relation\n * cells, the list-empty Create CTA, …). Without it the framework falls back to\n * a raw `<a>` — correct, but a full document reload on every click.\n *\n * This is a link component and nothing else: the location and imperative\n * navigation are the `RouterAdapter`'s (`RouterAdapterProvider` and\n * `useRouteNavigate` from `@burojs/react`), the one router seam.\n *\n * import Link from 'next/link';\n * <LinkComponentProvider linkComponent={Link}>\n * <App />\n * </LinkComponentProvider>\n */\nexport function LinkComponentProvider({\n linkComponent,\n children,\n}: LinkComponentProviderProps): ReactElement {\n return (\n <LinkComponentContext.Provider value={linkComponent}>{children}</LinkComponentContext.Provider>\n );\n}\n\n/**\n * Read the current `LinkComponent`. Call this from any framework\n * component that needs to render an internal-navigation link, then\n * use the returned component in JSX:\n *\n * const RouterLink = useLinkComponent();\n * return <RouterLink href={href}>{label}</RouterLink>;\n *\n * Always returns a usable component — the default fallback emits a\n * raw `<a href>` so the component never has to null-check.\n */\nexport function useLinkComponent(): LinkComponent {\n return useContext(LinkComponentContext);\n}\n","/**\n * Turn a developer-facing error string into something a user can act on.\n *\n * Two surfaces need this and they are reached by different code paths — the\n * notification provider (a legacy query adapter's default query-error\n * toast) and the inline\n * error panel a list renders in place of its rows. Fixing only the first left\n * `/document_document` still printing a raw English «Failed to fetch» under a\n * Russian heading, which is how this ended up in a second acceptance round.\n * One helper, both call sites.\n *\n * Anything we do not recognise is passed through unchanged: a message written\n * by the adopter's own `onError` is almost certainly better than a generic\n * substitute.\n */\n\n/** A legacy query adapter's own untranslated wording plus the raw fetch/network failures. */\nconst DEV_FACING =\n /^Error \\(status code:|^Failed to fetch$|^NetworkError|^Load failed$|^TypeError: (Failed to fetch|NetworkError)/i;\nconst NETWORK = /Failed to fetch|NetworkError|Load failed|ERR_INTERNET_DISCONNECTED/i;\n/**\n * A machine transport trace, e.g.\n * «Temporal GET /namespaces/default/schedules?maximumPageSize=10 -> 502»\n *\n * A data provider that reports which call failed is doing the right thing —\n * that string belongs in a bug report. It does not belong in front of an\n * operator as the entire explanation of why their page is empty, which is\n * exactly what `/schedules` did when the Temporal cluster went down.\n *\n * Recognised by an HTTP verb followed by an arrow and a status code; nothing\n * an adopter would hand-write looks like this, so the pass-through contract\n * for adopter-authored messages is preserved.\n */\nconst HTTP_TRACE = /\\b(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\\b[^\\n]*->\\s*(\\d{3})\\b/;\n\n/** The four sentences a recognised failure is replaced with: the bundle keys `requestError.*`. */\nexport interface FriendlyErrorStrings {\n readonly title: string;\n readonly offline: string;\n readonly unavailable: string;\n readonly generic: string;\n}\n\n/**\n * The framework's code language: used where no i18n is configured, and for any\n * key the active bundles do not carry.\n */\nexport const en: FriendlyErrorStrings = {\n title: 'Request failed',\n offline: 'Cannot reach the server. Check your connection and try again.',\n unavailable: 'The service is temporarily unavailable. Try again in a minute.',\n generic: 'Please try again.',\n};\n\n/**\n * Resolve {@link FriendlyErrorStrings} through a translate function (`useT()`,\n * `useOptionalT()`), taking the English default for each key it cannot find.\n */\nexport function friendlyStringsFrom(\n t: (key: string, options?: { defaultValue?: string }) => string,\n): FriendlyErrorStrings {\n const pick = (name: keyof FriendlyErrorStrings): string =>\n t(`requestError.${name}`, { defaultValue: en[name] }) || en[name];\n return {\n title: pick('title'),\n offline: pick('offline'),\n unavailable: pick('unavailable'),\n generic: pick('generic'),\n };\n}\n\nexport interface FriendlyError {\n /** Short headline. Empty when the original message should be kept. */\n title: string;\n /** Explanation the user can act on. */\n detail: string;\n /** True when the input was recognised as developer-facing and replaced. */\n replaced: boolean;\n}\n\n/**\n * @param strings the sentences to substitute, already in the active language:\n * {@link friendlyStringsFrom} builds them from the app's translate function,\n * and {@link en} is the default where none is configured.\n */\nexport function toFriendlyError(\n message: unknown,\n description: unknown,\n strings: FriendlyErrorStrings,\n): FriendlyError {\n const raw = `${String(message ?? '')} ${String(description ?? '')}`;\n const isDevFacing = DEV_FACING.test(String(message ?? '').trim());\n const isNetwork = NETWORK.test(raw);\n const trace = HTTP_TRACE.exec(String(message ?? '').trim());\n\n if (trace) {\n // 5xx is the server's problem and waiting is the right advice; anything\n // else is at least not something the operator can read a URL out of.\n const status = Number(trace[1]);\n return {\n title: strings.title,\n detail: status >= 500 ? strings.unavailable : strings.generic,\n replaced: true,\n };\n }\n\n if (!isDevFacing && !isNetwork) {\n return {\n title: String(message ?? ''),\n detail: String(description ?? ''),\n replaced: false,\n };\n }\n return {\n title: strings.title,\n detail: isNetwork ? strings.offline : String(description ?? '') || strings.generic,\n replaced: true,\n };\n}\n\n/** Whether a technical text says the network itself failed (a dead connection, a refused fetch). */\nexport function isNetworkFailure(text: string): boolean {\n return NETWORK.test(text);\n}\n\n/**\n * Every developer-facing text a failure carries, most specific first: string `details`, else\n * `details.message`; then an `Error` cause's message or a string cause; then the message of the\n * error itself when it is a plain `Error`. A provider's `details` is often its own sentence\n * (\"did not answer a mutation; it may have been applied\"), so the thrown fetch error in `cause`\n * must stay readable behind it. A `DataError`'s `message` is a reference a user may read; this is\n * what it no longer carries.\n */\nexport function technicalTexts(error: unknown): readonly string[] {\n if (error === null || typeof error !== 'object') return [];\n const out: string[] = [];\n try {\n const { details, cause } = error as { readonly details?: unknown; readonly cause?: unknown };\n if (typeof details === 'string') out.push(details);\n else if (details !== null && typeof details === 'object') {\n const inner = (details as { readonly message?: unknown }).message;\n if (typeof inner === 'string') out.push(inner);\n }\n if (cause instanceof Error) out.push(cause.message);\n // The normaliser moves a legacy string `message` into `cause`.\n else if (typeof cause === 'string') out.push(cause);\n if (error instanceof Error) out.push(error.message);\n } catch {\n // A getter that throws on a hostile error object: there is no more technical text to read.\n }\n return out;\n}\n\n/** The first of {@link technicalTexts}: for the friendly path and development logs only. */\nexport function technicalText(error: unknown): string | undefined {\n return technicalTexts(error)[0];\n}\n","import type { NotificationSinkMessage } from '@burojs/react';\nimport { toast } from 'sonner';\nimport { type FriendlyErrorStrings, en, toFriendlyError } from '../lib/friendly-error.js';\n\n/** `<Toaster>`'s sink: `@burojs/react`'s `NotificationSink`, plus `close`. */\nexport interface NotificationSink {\n open(args: NotificationSinkMessage): void;\n close(key: string): void;\n}\n\n/**\n * A legacy query adapter can build its default query-error notification outside\n * our i18n bundle: the user saw «Error (status code: undefined)» / «Failed to fetch», plus it duplicated\n * the inline error state a list already renders. We can't localize a string we\n * don't own, so `toFriendlyError` recognises it and substitutes plain language.\n * The SAME helper backs the list's inline panel — see friendly-error.ts for why\n * that matters.\n */\nexport interface NotificationProviderOptions {\n /**\n * The sentences an error toast is rewritten with (`requestError.*` in the active bundle), or\n * `undefined` for the English defaults. `<Toaster>` passes one built from the app's translate\n * function; a sink built outside React passes its own.\n *\n * ⚠️ It is a FUNCTION, not a value, and that is load-bearing: both examples\n * build this sink once (`React.useMemo(…, [])`) and it has no React context\n * to subscribe to, so strings captured at construction would freeze in\n * whatever language was active on the first render. Called per toast instead.\n */\n readonly resolveStrings?: () => FriendlyErrorStrings | undefined;\n}\n\nexport function createNotificationProvider(\n options: NotificationProviderOptions = {},\n): NotificationSink {\n return {\n open: ({ type, message, description }) => {\n let text = message;\n let detail = description;\n if (type === 'error') {\n const friendly = toFriendlyError(message, description, options.resolveStrings?.() ?? en);\n text = friendly.title;\n detail = friendly.detail;\n }\n // `info` is a plain notice that dismisses itself — never `toast.loading`,\n // whose spinner stays on screen until someone closes it.\n const fn = type === 'error' ? toast.error : type === 'info' ? toast.info : toast.success;\n fn(text, { description: detail });\n },\n close: (key: string) => {\n toast.dismiss(key);\n },\n };\n}\n","'use client';\nimport { useOptionalT, useRegisterNotificationSink } from '@burojs/react';\nimport { useMemo, useRef } from 'react';\nimport { Toaster as SonnerToaster, type ToasterProps as SonnerProps, toast } from 'sonner';\nimport { friendlyStringsFrom } from '../lib/friendly-error.js';\nimport { useTheme } from '../layout/use-theme.js';\nimport { createNotificationProvider } from './notification-provider.js';\n\nexport interface ToasterProps extends SonnerProps {}\n\n/**\n * Shadcn-canonical Sonner setup, and — inside a `<BuroProvider>` — the app's\n * notification sink: the dispatcher's default `notify` (every handler's\n * `notify`, and a thrown handler's error) is delivered here. Outside a\n * provider it registers nothing and still renders anything sent with `toast()`.\n *\n * Bound to design tokens via `toastOptions.classNames`, so toasts inherit the\n * app's light/dark theme through the same CSS-variable cascade as the rest of\n * the UI; `theme` is reactive via `useTheme()`. The error sentences come from\n * the ACTIVE bundle (`requestError.*`), read per toast so a language switch is\n * honoured; with no i18n provider they are the English defaults.\n */\nexport function Toaster(props: ToasterProps): React.ReactElement {\n // `useOptionalT`: the toaster may sit outside a BuroProvider, so it must\n // degrade rather than throw.\n const t = useOptionalT();\n const { resolved: theme } = useTheme();\n const tRef = useRef(t);\n tRef.current = t;\n const sink = useMemo(\n () => createNotificationProvider({ resolveStrings: () => friendlyStringsFrom(tRef.current) }),\n [],\n );\n useRegisterNotificationSink(sink);\n\n const { toastOptions, ...rest } = props;\n const kitClassNames = {\n toast:\n 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',\n description: 'group-[.toast]:text-muted-foreground',\n actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',\n cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',\n closeButton:\n 'group-[.toast]:bg-background group-[.toast]:text-muted-foreground group-[.toast]:border-border group-[.toast]:hover:bg-muted group-[.toast]:hover:text-foreground',\n };\n\n return (\n <SonnerToaster\n /*\n ⚠️ Sonner's default accessible name is \"Notifications alt+T\" and reaches\n the accessibility tree on every page. `customAriaLabel` replaces it\n wholly (the hotkey hint leaves the name) with the adopter's own\n `customAriaLabel` or `containerAriaLabel` (sonner 2 ignores the latter\n once the former is set, so it is honoured here), else the bundle's\n `notifications.region`. The literal fallback is English, like every\n framework default.\n */\n customAriaLabel={\n props.customAriaLabel ??\n props.containerAriaLabel ??\n t('notifications.region', { defaultValue: 'Notifications' })\n }\n theme={theme}\n position=\"top-right\"\n closeButton\n className=\"toaster group\"\n {...rest}\n /*\n The adopter's `toastOptions` merge with ours instead of replacing them:\n the adopter wins per key, and per slot inside `classNames`. The close\n button's label is the bundle's `notifications.close` unless the adopter\n names their own.\n */\n toastOptions={{\n ...toastOptions,\n classNames: { ...kitClassNames, ...toastOptions?.classNames },\n closeButtonAriaLabel:\n toastOptions?.closeButtonAriaLabel ??\n t('notifications.close', { defaultValue: 'Close notification' }),\n }}\n />\n );\n}\n\nexport { toast };\n"],"mappings":"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"}
@@ -49,11 +49,7 @@ const en = {
49
49
  generic: "Please try again."
50
50
  };
51
51
  function friendlyStringsFrom(t) {
52
- const pick = (name) => {
53
- const key = `requestError.${name}`;
54
- const value = t(key);
55
- return value && value !== key ? value : en[name];
56
- };
52
+ const pick = (name) => t(`requestError.${name}`, { defaultValue: en[name] }) || en[name];
57
53
  return {
58
54
  title: pick("title"),
59
55
  offline: pick("offline"),
@@ -85,6 +81,28 @@ function toFriendlyError(message, description, strings) {
85
81
  replaced: true
86
82
  };
87
83
  }
84
+ function isNetworkFailure(text) {
85
+ return NETWORK.test(text);
86
+ }
87
+ function technicalTexts(error) {
88
+ if (error === null || typeof error !== "object") return [];
89
+ const out = [];
90
+ try {
91
+ const { details, cause } = error;
92
+ if (typeof details === "string") out.push(details);
93
+ else if (details !== null && typeof details === "object") {
94
+ const inner = details.message;
95
+ if (typeof inner === "string") out.push(inner);
96
+ }
97
+ if (cause instanceof Error) out.push(cause.message);
98
+ else if (typeof cause === "string") out.push(cause);
99
+ if (error instanceof Error) out.push(error.message);
100
+ } catch {}
101
+ return out;
102
+ }
103
+ function technicalText(error) {
104
+ return technicalTexts(error)[0];
105
+ }
88
106
  //#endregion
89
107
  //#region src/notification/notification-provider.ts
90
108
  function createNotificationProvider(options = {}) {
@@ -113,23 +131,32 @@ function Toaster$1(props) {
113
131
  tRef.current = t;
114
132
  const sink = useMemo(() => createNotificationProvider({ resolveStrings: () => friendlyStringsFrom(tRef.current) }), []);
115
133
  useRegisterNotificationSink(sink);
134
+ const { toastOptions, ...rest } = props;
135
+ const kitClassNames = {
136
+ toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
137
+ description: "group-[.toast]:text-muted-foreground",
138
+ actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
139
+ cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
140
+ closeButton: "group-[.toast]:bg-background group-[.toast]:text-muted-foreground group-[.toast]:border-border group-[.toast]:hover:bg-muted group-[.toast]:hover:text-foreground"
141
+ };
116
142
  return jsx(Toaster, {
117
- containerAriaLabel: t("notifications.region", { defaultValue: "Notifications" }),
143
+ customAriaLabel: props.customAriaLabel ?? props.containerAriaLabel ?? t("notifications.region", { defaultValue: "Notifications" }),
118
144
  theme,
119
145
  position: "top-right",
120
146
  closeButton: true,
121
147
  className: "toaster group",
122
- toastOptions: { classNames: {
123
- toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
124
- description: "group-[.toast]:text-muted-foreground",
125
- actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
126
- cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
127
- closeButton: "group-[.toast]:bg-background group-[.toast]:text-muted-foreground group-[.toast]:border-border group-[.toast]:hover:bg-muted group-[.toast]:hover:text-foreground"
128
- } },
129
- ...props
148
+ ...rest,
149
+ toastOptions: {
150
+ ...toastOptions,
151
+ classNames: {
152
+ ...kitClassNames,
153
+ ...toastOptions?.classNames
154
+ },
155
+ closeButtonAriaLabel: toastOptions?.closeButtonAriaLabel ?? t("notifications.close", { defaultValue: "Close notification" })
156
+ }
130
157
  });
131
158
  }
132
159
  //#endregion
133
- export { LinkComponentProvider as a, PopoverAnchor as c, PopoverTrigger as d, toFriendlyError as i, PopoverClose as l, toast$1 as n, useLinkComponent as o, friendlyStringsFrom as r, Popover as s, Toaster$1 as t, PopoverContent as u };
160
+ export { technicalText as a, LinkComponentProvider as c, PopoverAnchor as d, PopoverClose as f, isNetworkFailure as i, useLinkComponent as l, PopoverTrigger as m, toast$1 as n, technicalTexts as o, PopoverContent as p, friendlyStringsFrom as r, toFriendlyError as s, Toaster$1 as t, Popover as u };
134
161
 
135
- //# sourceMappingURL=toaster-C02XbB9r.js.map
162
+ //# sourceMappingURL=toaster-Ct_YHGs6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toaster-Ct_YHGs6.js","names":["Toaster","SonnerToaster"],"sources":["../src/primitives/popover.tsx","../src/layout/link-component.tsx","../src/lib/friendly-error.ts","../src/notification/notification-provider.ts","../src/notification/toaster.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\nimport { cn } from '../lib/cn.js';\n\n/**\n * Thin wrapper around Radix Popover, mirroring the structure of the\n * Dialog / Select primitives in this package so the Tailwind tokens\n * stay aligned. Re-exports `Root` / `Trigger` / `Anchor` / `Close`\n * directly (no styling needed) and styles `Content` to match the\n * shadcn Popover defaults.\n */\n\nconst Popover = PopoverPrimitive.Root;\nconst PopoverTrigger = PopoverPrimitive.Trigger;\nconst PopoverAnchor = PopoverPrimitive.Anchor;\nconst PopoverClose = PopoverPrimitive.Close;\n\nexport type PopoverContentProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>;\n\nconst PopoverContent = React.forwardRef<\n React.ElementRef<typeof PopoverPrimitive.Content>,\n PopoverContentProps\n>(({ className, align = 'start', sideOffset = 4, ...props }, ref) => (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n className={cn(\n 'z-50 w-72 rounded-md border bg-popover p-3 text-popover-foreground shadow-md outline-hidden',\n 'duration-[var(--duration-overlay)] ease-[var(--ease-overlay)]',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n className,\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n));\nPopoverContent.displayName = PopoverPrimitive.Content.displayName;\n\nexport { Popover, PopoverTrigger, PopoverAnchor, PopoverClose, PopoverContent };\n","'use client';\nimport {\n type AnchorHTMLAttributes,\n type ComponentType,\n type ReactElement,\n type ReactNode,\n createContext,\n forwardRef,\n useContext,\n} from 'react';\n\n/**\n * Props every `LinkComponent` must accept. We intentionally mirror the\n * native `<a>` API surface so adopters writing a small wrapper around\n * their router's link primitive (next/link, react-router, TanStack\n * Router, etc.) can pass them straight through.\n *\n * `next/link` already accepts these names verbatim. `react-router`'s\n * `<Link>` uses `to` instead of `href` — adopters wrap once:\n *\n * const RouterLink: LinkComponent = ({ href, ...rest }) =>\n * <Link to={href} {...rest} />;\n */\nexport type LinkComponentProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> & {\n href: string;\n children?: ReactNode;\n};\n\nexport type LinkComponent = ComponentType<LinkComponentProps>;\n\n/**\n * Default fallback — a plain `<a href>` anchor. Triggers a full\n * document load. Adopters that ship a SPA router (Next.js App Router,\n * react-router) install their router's `<Link>` via\n * `<LinkComponentProvider>` so internal navigation stays client-side.\n */\nconst DefaultLinkComponent: LinkComponent = forwardRef<HTMLAnchorElement, LinkComponentProps>(\n function DefaultLinkComponent({ children, ...rest }, ref) {\n return (\n <a ref={ref} {...rest}>\n {children}\n </a>\n );\n },\n);\n\nconst LinkComponentContext = createContext<LinkComponent>(DefaultLinkComponent);\n\nexport interface LinkComponentProviderProps {\n /**\n * The router-aware Link component to use throughout `@burojs/ui-shadcn` for\n * internal navigation. Pass `Link` from `next/link` for Next.js App Router;\n * wrap your router's link primitive once for react-router / TanStack Router.\n */\n linkComponent: LinkComponent;\n children: ReactNode;\n}\n\n/**\n * Install a router-aware `LinkComponent` for every framework component that\n * renders an internal navigation link (sidebar entries, action links, relation\n * cells, the list-empty Create CTA, …). Without it the framework falls back to\n * a raw `<a>` — correct, but a full document reload on every click.\n *\n * This is a link component and nothing else: the location and imperative\n * navigation are the `RouterAdapter`'s (`RouterAdapterProvider` and\n * `useRouteNavigate` from `@burojs/react`), the one router seam.\n *\n * import Link from 'next/link';\n * <LinkComponentProvider linkComponent={Link}>\n * <App />\n * </LinkComponentProvider>\n */\nexport function LinkComponentProvider({\n linkComponent,\n children,\n}: LinkComponentProviderProps): ReactElement {\n return (\n <LinkComponentContext.Provider value={linkComponent}>{children}</LinkComponentContext.Provider>\n );\n}\n\n/**\n * Read the current `LinkComponent`. Call this from any framework\n * component that needs to render an internal-navigation link, then\n * use the returned component in JSX:\n *\n * const RouterLink = useLinkComponent();\n * return <RouterLink href={href}>{label}</RouterLink>;\n *\n * Always returns a usable component — the default fallback emits a\n * raw `<a href>` so the component never has to null-check.\n */\nexport function useLinkComponent(): LinkComponent {\n return useContext(LinkComponentContext);\n}\n","/**\n * Turn a developer-facing error string into something a user can act on.\n *\n * Two surfaces need this and they are reached by different code paths — the\n * notification provider (a legacy query adapter's default query-error\n * toast) and the inline\n * error panel a list renders in place of its rows. Fixing only the first left\n * `/document_document` still printing a raw English «Failed to fetch» under a\n * Russian heading, which is how this ended up in a second acceptance round.\n * One helper, both call sites.\n *\n * Anything we do not recognise is passed through unchanged: a message written\n * by the adopter's own `onError` is almost certainly better than a generic\n * substitute.\n */\n\n/** A legacy query adapter's own untranslated wording plus the raw fetch/network failures. */\nconst DEV_FACING =\n /^Error \\(status code:|^Failed to fetch$|^NetworkError|^Load failed$|^TypeError: (Failed to fetch|NetworkError)/i;\nconst NETWORK = /Failed to fetch|NetworkError|Load failed|ERR_INTERNET_DISCONNECTED/i;\n/**\n * A machine transport trace, e.g.\n * «Temporal GET /namespaces/default/schedules?maximumPageSize=10 -> 502»\n *\n * A data provider that reports which call failed is doing the right thing —\n * that string belongs in a bug report. It does not belong in front of an\n * operator as the entire explanation of why their page is empty, which is\n * exactly what `/schedules` did when the Temporal cluster went down.\n *\n * Recognised by an HTTP verb followed by an arrow and a status code; nothing\n * an adopter would hand-write looks like this, so the pass-through contract\n * for adopter-authored messages is preserved.\n */\nconst HTTP_TRACE = /\\b(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\\b[^\\n]*->\\s*(\\d{3})\\b/;\n\n/** The four sentences a recognised failure is replaced with: the bundle keys `requestError.*`. */\nexport interface FriendlyErrorStrings {\n readonly title: string;\n readonly offline: string;\n readonly unavailable: string;\n readonly generic: string;\n}\n\n/**\n * The framework's code language: used where no i18n is configured, and for any\n * key the active bundles do not carry.\n */\nexport const en: FriendlyErrorStrings = {\n title: 'Request failed',\n offline: 'Cannot reach the server. Check your connection and try again.',\n unavailable: 'The service is temporarily unavailable. Try again in a minute.',\n generic: 'Please try again.',\n};\n\n/**\n * Resolve {@link FriendlyErrorStrings} through a translate function (`useT()`,\n * `useOptionalT()`), taking the English default for each key it cannot find.\n */\nexport function friendlyStringsFrom(\n t: (key: string, options?: { defaultValue?: string }) => string,\n): FriendlyErrorStrings {\n const pick = (name: keyof FriendlyErrorStrings): string =>\n t(`requestError.${name}`, { defaultValue: en[name] }) || en[name];\n return {\n title: pick('title'),\n offline: pick('offline'),\n unavailable: pick('unavailable'),\n generic: pick('generic'),\n };\n}\n\nexport interface FriendlyError {\n /** Short headline. Empty when the original message should be kept. */\n title: string;\n /** Explanation the user can act on. */\n detail: string;\n /** True when the input was recognised as developer-facing and replaced. */\n replaced: boolean;\n}\n\n/**\n * @param strings the sentences to substitute, already in the active language:\n * {@link friendlyStringsFrom} builds them from the app's translate function,\n * and {@link en} is the default where none is configured.\n */\nexport function toFriendlyError(\n message: unknown,\n description: unknown,\n strings: FriendlyErrorStrings,\n): FriendlyError {\n const raw = `${String(message ?? '')} ${String(description ?? '')}`;\n const isDevFacing = DEV_FACING.test(String(message ?? '').trim());\n const isNetwork = NETWORK.test(raw);\n const trace = HTTP_TRACE.exec(String(message ?? '').trim());\n\n if (trace) {\n // 5xx is the server's problem and waiting is the right advice; anything\n // else is at least not something the operator can read a URL out of.\n const status = Number(trace[1]);\n return {\n title: strings.title,\n detail: status >= 500 ? strings.unavailable : strings.generic,\n replaced: true,\n };\n }\n\n if (!isDevFacing && !isNetwork) {\n return {\n title: String(message ?? ''),\n detail: String(description ?? ''),\n replaced: false,\n };\n }\n return {\n title: strings.title,\n detail: isNetwork ? strings.offline : String(description ?? '') || strings.generic,\n replaced: true,\n };\n}\n\n/** Whether a technical text says the network itself failed (a dead connection, a refused fetch). */\nexport function isNetworkFailure(text: string): boolean {\n return NETWORK.test(text);\n}\n\n/**\n * Every developer-facing text a failure carries, most specific first: string `details`, else\n * `details.message`; then an `Error` cause's message or a string cause; then the message of the\n * error itself when it is a plain `Error`. A provider's `details` is often its own sentence\n * (\"did not answer a mutation; it may have been applied\"), so the thrown fetch error in `cause`\n * must stay readable behind it. A `DataError`'s `message` is a reference a user may read; this is\n * what it no longer carries.\n */\nexport function technicalTexts(error: unknown): readonly string[] {\n if (error === null || typeof error !== 'object') return [];\n const out: string[] = [];\n try {\n const { details, cause } = error as { readonly details?: unknown; readonly cause?: unknown };\n if (typeof details === 'string') out.push(details);\n else if (details !== null && typeof details === 'object') {\n const inner = (details as { readonly message?: unknown }).message;\n if (typeof inner === 'string') out.push(inner);\n }\n if (cause instanceof Error) out.push(cause.message);\n // The normaliser moves a legacy string `message` into `cause`.\n else if (typeof cause === 'string') out.push(cause);\n if (error instanceof Error) out.push(error.message);\n } catch {\n // A getter that throws on a hostile error object: there is no more technical text to read.\n }\n return out;\n}\n\n/** The first of {@link technicalTexts}: for the friendly path and development logs only. */\nexport function technicalText(error: unknown): string | undefined {\n return technicalTexts(error)[0];\n}\n","import type { NotificationSinkMessage } from '@burojs/react';\nimport { toast } from 'sonner';\nimport { type FriendlyErrorStrings, en, toFriendlyError } from '../lib/friendly-error.js';\n\n/** `<Toaster>`'s sink: `@burojs/react`'s `NotificationSink`, plus `close`. */\nexport interface NotificationSink {\n open(args: NotificationSinkMessage): void;\n close(key: string): void;\n}\n\n/**\n * A legacy query adapter can build its default query-error notification outside\n * our i18n bundle: the user saw «Error (status code: undefined)» / «Failed to fetch», plus it duplicated\n * the inline error state a list already renders. We can't localize a string we\n * don't own, so `toFriendlyError` recognises it and substitutes plain language.\n * The SAME helper backs the list's inline panel — see friendly-error.ts for why\n * that matters.\n */\nexport interface NotificationProviderOptions {\n /**\n * The sentences an error toast is rewritten with (`requestError.*` in the active bundle), or\n * `undefined` for the English defaults. `<Toaster>` passes one built from the app's translate\n * function; a sink built outside React passes its own.\n *\n * ⚠️ It is a FUNCTION, not a value, and that is load-bearing: both examples\n * build this sink once (`React.useMemo(…, [])`) and it has no React context\n * to subscribe to, so strings captured at construction would freeze in\n * whatever language was active on the first render. Called per toast instead.\n */\n readonly resolveStrings?: () => FriendlyErrorStrings | undefined;\n}\n\nexport function createNotificationProvider(\n options: NotificationProviderOptions = {},\n): NotificationSink {\n return {\n open: ({ type, message, description }) => {\n let text = message;\n let detail = description;\n if (type === 'error') {\n const friendly = toFriendlyError(message, description, options.resolveStrings?.() ?? en);\n text = friendly.title;\n detail = friendly.detail;\n }\n // `info` is a plain notice that dismisses itself — never `toast.loading`,\n // whose spinner stays on screen until someone closes it.\n const fn = type === 'error' ? toast.error : type === 'info' ? toast.info : toast.success;\n fn(text, { description: detail });\n },\n close: (key: string) => {\n toast.dismiss(key);\n },\n };\n}\n","'use client';\nimport { useOptionalT, useRegisterNotificationSink } from '@burojs/react';\nimport { useMemo, useRef } from 'react';\nimport { Toaster as SonnerToaster, type ToasterProps as SonnerProps, toast } from 'sonner';\nimport { friendlyStringsFrom } from '../lib/friendly-error.js';\nimport { useTheme } from '../layout/use-theme.js';\nimport { createNotificationProvider } from './notification-provider.js';\n\nexport interface ToasterProps extends SonnerProps {}\n\n/**\n * Shadcn-canonical Sonner setup, and — inside a `<BuroProvider>` — the app's\n * notification sink: the dispatcher's default `notify` (every handler's\n * `notify`, and a thrown handler's error) is delivered here. Outside a\n * provider it registers nothing and still renders anything sent with `toast()`.\n *\n * Bound to design tokens via `toastOptions.classNames`, so toasts inherit the\n * app's light/dark theme through the same CSS-variable cascade as the rest of\n * the UI; `theme` is reactive via `useTheme()`. The error sentences come from\n * the ACTIVE bundle (`requestError.*`), read per toast so a language switch is\n * honoured; with no i18n provider they are the English defaults.\n */\nexport function Toaster(props: ToasterProps): React.ReactElement {\n // `useOptionalT`: the toaster may sit outside a BuroProvider, so it must\n // degrade rather than throw.\n const t = useOptionalT();\n const { resolved: theme } = useTheme();\n const tRef = useRef(t);\n tRef.current = t;\n const sink = useMemo(\n () => createNotificationProvider({ resolveStrings: () => friendlyStringsFrom(tRef.current) }),\n [],\n );\n useRegisterNotificationSink(sink);\n\n const { toastOptions, ...rest } = props;\n const kitClassNames = {\n toast:\n 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',\n description: 'group-[.toast]:text-muted-foreground',\n actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',\n cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',\n closeButton:\n 'group-[.toast]:bg-background group-[.toast]:text-muted-foreground group-[.toast]:border-border group-[.toast]:hover:bg-muted group-[.toast]:hover:text-foreground',\n };\n\n return (\n <SonnerToaster\n /*\n ⚠️ Sonner's default accessible name is \"Notifications alt+T\" and reaches\n the accessibility tree on every page. `customAriaLabel` replaces it\n wholly (the hotkey hint leaves the name) with the adopter's own\n `customAriaLabel` or `containerAriaLabel` (sonner 2 ignores the latter\n once the former is set, so it is honoured here), else the bundle's\n `notifications.region`. The literal fallback is English, like every\n framework default.\n */\n customAriaLabel={\n props.customAriaLabel ??\n props.containerAriaLabel ??\n t('notifications.region', { defaultValue: 'Notifications' })\n }\n theme={theme}\n position=\"top-right\"\n closeButton\n className=\"toaster group\"\n {...rest}\n /*\n The adopter's `toastOptions` merge with ours instead of replacing them:\n the adopter wins per key, and per slot inside `classNames`. The close\n button's label is the bundle's `notifications.close` unless the adopter\n names their own.\n */\n toastOptions={{\n ...toastOptions,\n classNames: { ...kitClassNames, ...toastOptions?.classNames },\n closeButtonAriaLabel:\n toastOptions?.closeButtonAriaLabel ??\n t('notifications.close', { defaultValue: 'Close notification' }),\n }}\n />\n );\n}\n\nexport { toast };\n"],"mappings":";;;;;;;;AAcA,MAAM,UAAU,iBAAiB;AACjC,MAAM,iBAAiB,iBAAiB;AACxC,MAAM,gBAAgB,iBAAiB;AACvC,MAAM,eAAe,iBAAiB;AAItC,MAAM,iBAAiB,MAAM,YAG1B,EAAE,WAAW,QAAQ,SAAS,aAAa,GAAG,GAAG,SAAS,QAC3D,IAAC,iBAAiB,QAAlB,EAAA,UACE,IAAC,iBAAiB,SAAlB;CACO;CACE;CACK;CACZ,WAAW,GACT,+FACA,iEACA,gEACA,8DACA,gEACA,+JACA,SACF;CACA,GAAI;AACL,CAAA,EACsB,CAAA,CAC1B;AACD,eAAe,cAAc,iBAAiB,QAAQ;;;ACPtD,MAAM,uBAAsC,WAC1C,SAAS,qBAAqB,EAAE,UAAU,GAAG,QAAQ,KAAK;CACxD,OACE,IAAC,KAAD;EAAQ;EAAK,GAAI;EACd;CACA,CAAA;AAEP,CACF;AAEA,MAAM,uBAAuB,cAA6B,oBAAoB;AA2B9E,SAAgB,sBAAsB,EACpC,eACA,YAC2C;CAC3C,OACE,IAAC,qBAAqB,UAAtB;EAA+B,OAAO;EAAgB;CAAwC,CAAA;AAElG;AAaA,SAAgB,mBAAkC;CAChD,OAAO,WAAW,oBAAoB;AACxC;;;AC9EA,MAAM,aACJ;AACF,MAAM,UAAU;AAchB,MAAM,aAAa;AAcnB,MAAa,KAA2B;CACtC,OAAO;CACP,SAAS;CACT,aAAa;CACb,SAAS;AACX;AAMA,SAAgB,oBACd,GACsB;CACtB,MAAM,QAAQ,SACZ,EAAE,gBAAgB,QAAQ,EAAE,cAAc,GAAG,MAAM,CAAC,KAAK,GAAG;CAC9D,OAAO;EACL,OAAO,KAAK,OAAO;EACnB,SAAS,KAAK,SAAS;EACvB,aAAa,KAAK,aAAa;EAC/B,SAAS,KAAK,SAAS;CACzB;AACF;AAgBA,SAAgB,gBACd,SACA,aACA,SACe;CACf,MAAM,MAAM,GAAG,OAAO,WAAW,EAAE,EAAE,GAAG,OAAO,eAAe,EAAE;CAChE,MAAM,cAAc,WAAW,KAAK,OAAO,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC;CAChE,MAAM,YAAY,QAAQ,KAAK,GAAG;CAClC,MAAM,QAAQ,WAAW,KAAK,OAAO,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC;CAE1D,IAAI,OAAO;EAGT,MAAM,SAAS,OAAO,MAAM,EAAE;EAC9B,OAAO;GACL,OAAO,QAAQ;GACf,QAAQ,UAAU,MAAM,QAAQ,cAAc,QAAQ;GACtD,UAAU;EACZ;CACF;CAEA,IAAI,CAAC,eAAe,CAAC,WACnB,OAAO;EACL,OAAO,OAAO,WAAW,EAAE;EAC3B,QAAQ,OAAO,eAAe,EAAE;EAChC,UAAU;CACZ;CAEF,OAAO;EACL,OAAO,QAAQ;EACf,QAAQ,YAAY,QAAQ,UAAU,OAAO,eAAe,EAAE,KAAK,QAAQ;EAC3E,UAAU;CACZ;AACF;AAGA,SAAgB,iBAAiB,MAAuB;CACtD,OAAO,QAAQ,KAAK,IAAI;AAC1B;AAUA,SAAgB,eAAe,OAAmC;CAChE,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO,CAAC;CACzD,MAAM,MAAgB,CAAC;CACvB,IAAI;EACF,MAAM,EAAE,SAAS,UAAU;EAC3B,IAAI,OAAO,YAAY,UAAU,IAAI,KAAK,OAAO;OAC5C,IAAI,YAAY,QAAQ,OAAO,YAAY,UAAU;GACxD,MAAM,QAAS,QAA2C;GAC1D,IAAI,OAAO,UAAU,UAAU,IAAI,KAAK,KAAK;EAC/C;EACA,IAAI,iBAAiB,OAAO,IAAI,KAAK,MAAM,OAAO;OAE7C,IAAI,OAAO,UAAU,UAAU,IAAI,KAAK,KAAK;EAClD,IAAI,iBAAiB,OAAO,IAAI,KAAK,MAAM,OAAO;CACpD,QAAQ,CAER;CACA,OAAO;AACT;AAGA,SAAgB,cAAc,OAAoC;CAChE,OAAO,eAAe,KAAK,CAAC,CAAC;AAC/B;;;AC5HA,SAAgB,2BACd,UAAuC,CAAC,GACtB;CAClB,OAAO;EACL,OAAO,EAAE,MAAM,SAAS,kBAAkB;GACxC,IAAI,OAAO;GACX,IAAI,SAAS;GACb,IAAI,SAAS,SAAS;IACpB,MAAM,WAAW,gBAAgB,SAAS,aAAa,QAAQ,iBAAiB,KAAK,EAAE;IACvF,OAAO,SAAS;IAChB,SAAS,SAAS;GACpB;GAIA,CADW,SAAS,UAAU,MAAM,QAAQ,SAAS,SAAS,MAAM,OAAO,MAAM,QAAA,CAC9E,MAAM,EAAE,aAAa,OAAO,CAAC;EAClC;EACA,QAAQ,QAAgB;GACtB,MAAM,QAAQ,GAAG;EACnB;CACF;AACF;;;AC/BA,SAAgBA,UAAQ,OAAyC;CAG/D,MAAM,IAAI,aAAa;CACvB,MAAM,EAAE,UAAU,UAAU,SAAS;CACrC,MAAM,OAAO,OAAO,CAAC;CACrB,KAAK,UAAU;CACf,MAAM,OAAO,cACL,2BAA2B,EAAE,sBAAsB,oBAAoB,KAAK,OAAO,EAAE,CAAC,GAC5F,CAAC,CACH;CACA,4BAA4B,IAAI;CAEhC,MAAM,EAAE,cAAc,GAAG,SAAS;CAClC,MAAM,gBAAgB;EACpB,OACE;EACF,aAAa;EACb,cAAc;EACd,cAAc;EACd,aACE;CACJ;CAEA,OACE,IAACC,SAAD;EAUE,iBACE,MAAM,mBACN,MAAM,sBACN,EAAE,wBAAwB,EAAE,cAAc,gBAAgB,CAAC;EAEtD;EACP,UAAS;EACT,aAAA;EACA,WAAU;EACV,GAAI;EAOJ,cAAc;GACZ,GAAG;GACH,YAAY;IAAE,GAAG;IAAe,GAAG,cAAc;GAAW;GAC5D,sBACE,cAAc,wBACd,EAAE,uBAAuB,EAAE,cAAc,qBAAqB,CAAC;EACnE;CACD,CAAA;AAEL"}
@@ -1,9 +1,10 @@
1
- import { n as narrowSearchDecl, t as namesHiddenField } from "./search-access-CPPmfuvy.js";
2
- import { EMPTY_FILTER_GROUP, composeListFilter } from "@burojs/core";
1
+ import { a as namesHiddenField, o as narrowSearchDecl } from "./relation-create-C43cdPQt.js";
2
+ import { EMPTY_FILTER_GROUP, composeListFilter, routeResourceKey } from "@burojs/core";
3
3
  import { useApp, useFilterFields, useListAccess } from "@burojs/react";
4
4
  import { useCallback, useMemo, useState } from "react";
5
5
  //#region src/fields/use-dialog-filter-state.ts
6
- function useDialogFilterState(resource, options) {
6
+ function useDialogFilterState(resourceInput, options) {
7
+ const resource = routeResourceKey(resourceInput);
7
8
  const def = useApp().registry.get(resource);
8
9
  const { available, fixed } = useFilterFields(resource, {
9
10
  fixedFilters: options.fixedFilters,
@@ -84,4 +85,4 @@ function useDialogFilterState(resource, options) {
84
85
  //#endregion
85
86
  export { useDialogFilterState as t };
86
87
 
87
- //# sourceMappingURL=use-dialog-filter-state-C--oMAZG.js.map
88
+ //# sourceMappingURL=use-dialog-filter-state-B_MqzHaT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-dialog-filter-state-B_MqzHaT.js","names":[],"sources":["../src/fields/use-dialog-filter-state.ts"],"sourcesContent":["import {\n EMPTY_FILTER_GROUP,\n composeListFilter,\n type ErasedResourceHandle,\n type FilterExpression,\n type FilterGroup,\n type FixedFilterInput,\n type ResolvedFixedFilter,\n type SearchDecl,\n routeResourceKey,\n type ResourceNameOrRef,\n} from '@burojs/core';\nimport { useApp, useFilterFields, useListAccess, type FilterFieldSpec } from '@burojs/react';\nimport { namesHiddenField, narrowSearchDecl } from './search-access.js';\nimport { useCallback, useMemo, useState } from 'react';\n\n/** The portable erased AST — the only shape this module deals in. */\ntype ErasedExpression = FilterExpression<ErasedResourceHandle>;\n\n/** Options for {@link useDialogFilterState}. */\nexport interface UseDialogFilterStateOptions {\n /** Call-site narrowing (e.g. scope the target list to a product group). */\n readonly fixedFilters?: readonly FixedFilterInput[];\n /**\n * Fallback search field, used ONLY when the target resource declares no\n * `views.list.search`. Wrapped into a single-field `SearchFieldsDecl`\n * (`{ fields: [searchField] }`, implicit `contains` — same degenerate form\n * `searchToFilter` already gives a bare field-name string) so the override\n * goes through the exact same `composeListFilter` → `searchToFilter` path\n * as a real declaration, rather than a second hand-built predicate at the\n * call site. The target's own declaration always wins when present — this\n * is a fallback, not an override of a declared search.\n */\n readonly searchField?: string;\n /**\n * Gates the field-derivation walk (`useFilterFields`'s `categorizeField`\n * pass over every field of the resource). Defaults to `true`. A caller\n * whose UI isn't visible yet (e.g. `RelationPickerDialog` before it's\n * opened) should pass `false` to skip that work on every mount /\n * `fixedFilters`-identity change; the derivation recomputes in full the\n * next render where this flips back to `true`.\n */\n readonly enabled?: boolean;\n}\n\n/** Return shape of {@link useDialogFilterState}. */\nexport interface DialogFilterState {\n readonly group: FilterGroup;\n readonly setGroup: (next: FilterGroup) => void;\n readonly search: string;\n readonly setSearch: (next: string) => void;\n readonly page: number;\n readonly setPage: (page: number) => void;\n readonly available: readonly FilterFieldSpec[];\n readonly fixed: readonly ResolvedFixedFilter[];\n /** The single expression to hand the data provider. */\n readonly filter: ErasedExpression | undefined;\n /** Whether the target has a search for this user (no field left means no search box). */\n readonly searchEnabled: boolean;\n /** The read must not be sent: undecided access, or a filter naming a field the user may not view. */\n readonly blocked: boolean;\n readonly reset: () => void;\n}\n\n/**\n * Local (non-URL) filter state for the relation-picker's advanced-search\n * dialog.\n *\n * `useResourceList` cannot serve this: it unconditionally calls\n * `useResourceUrlState`, which throws without a `RouterAdapterProvider` and,\n * when one IS present, writes to `?<resource>.*`. A picker dialog mounted\n * inside a form for resource A, searching TARGET resource B, would then\n * write B's list state into the URL — clobbering a real B list page open on\n * another tab of the same app, and reading back whatever that page last\n * wrote on open. So this hook keeps `group`/`search`/`page` in plain\n * `useState` and reuses the SAME composition core's list controller uses\n * (`composeListFilter`) so the two never drift on what a filter means.\n *\n * No `pinned` slot: pinned controls are a list-PAGE affordance (permanent\n * screen real estate for a couple of always-visible facets); a modal has no\n * spare real estate to spend on them.\n */\nexport function useDialogFilterState<const N extends string = string>(\n resourceInput: ResourceNameOrRef<N>,\n options: UseDialogFilterStateOptions,\n): DialogFilterState {\n const resource = routeResourceKey(resourceInput);\n const app = useApp();\n const def = app.registry.get(resource);\n const { available, fixed } = useFilterFields(resource, {\n fixedFilters: options.fixedFilters,\n enabled: options.enabled,\n });\n\n const [group, setGroupState] = useState<FilterGroup>(EMPTY_FILTER_GROUP);\n const [search, setSearchState] = useState('');\n const [page, setPage] = useState(1);\n\n // Any change to WHAT is filtered invalidates the current page — the same\n // rule `useResourceList`'s URL-state controller applies to its own\n // group/search setters.\n const setGroup = useCallback((next: FilterGroup) => {\n setGroupState(next);\n setPage(1);\n }, []);\n\n const setSearch = useCallback((next: string) => {\n setSearchState(next);\n setPage(1);\n }, []);\n\n const reset = useCallback(() => {\n setGroupState(EMPTY_FILTER_GROUP);\n setSearchState('');\n setPage(1);\n }, []);\n\n // The target's own declaration always wins; `options.searchField` only\n // fills in when the target declares no search at all. Memoized so an\n // omitted declaration doesn't hand `composeListFilter`'s `filter` memo a\n // fresh `{ fields: [...] }` literal (and therefore a spurious recompute)\n // on every render — see `UseDialogFilterStateOptions.searchField`'s doc.\n const { searchField } = options;\n const access = useListAccess(resource);\n const accessReady = access.status === 'ready';\n const searchDecl = useMemo<SearchDecl | undefined>(() => {\n const declared = def?.views?.list?.search;\n const base =\n declared !== undefined\n ? declared\n : searchField === undefined\n ? undefined\n : { fields: [searchField] };\n return narrowSearchDecl(base, (field) => access.fieldVisible(field));\n }, [def, searchField, access]);\n const searchContext = useMemo(() => ({ resource, def }), [resource, def]);\n\n const filter = useMemo<ErasedExpression | undefined>(\n () => composeListFilter({ fixed, group, search, searchDecl, searchContext }),\n [fixed, group, search, searchDecl, searchContext],\n );\n\n // A read whose composed filter names a field the user may not view is forbidden (an author\n // predicate, or a function search's output); an undecided access sends nothing yet.\n const blocked = !accessReady || namesHiddenField(filter, (field) => access.fieldVisible(field));\n\n return useMemo<DialogFilterState>(\n () => ({\n searchEnabled: searchDecl !== undefined,\n blocked,\n group,\n setGroup,\n search,\n setSearch,\n page,\n setPage,\n available,\n fixed,\n filter,\n reset,\n }),\n [\n group,\n setGroup,\n search,\n setSearch,\n page,\n available,\n fixed,\n filter,\n reset,\n searchDecl,\n blocked,\n ],\n );\n}\n"],"mappings":";;;;;AAkFA,SAAgB,qBACd,eACA,SACmB;CACnB,MAAM,WAAW,iBAAiB,aAAa;CAE/C,MAAM,MADM,OACE,CAAC,CAAC,SAAS,IAAI,QAAQ;CACrC,MAAM,EAAE,WAAW,UAAU,gBAAgB,UAAU;EACrD,cAAc,QAAQ;EACtB,SAAS,QAAQ;CACnB,CAAC;CAED,MAAM,CAAC,OAAO,iBAAiB,SAAsB,kBAAkB;CACvE,MAAM,CAAC,QAAQ,kBAAkB,SAAS,EAAE;CAC5C,MAAM,CAAC,MAAM,WAAW,SAAS,CAAC;CAKlC,MAAM,WAAW,aAAa,SAAsB;EAClD,cAAc,IAAI;EAClB,QAAQ,CAAC;CACX,GAAG,CAAC,CAAC;CAEL,MAAM,YAAY,aAAa,SAAiB;EAC9C,eAAe,IAAI;EACnB,QAAQ,CAAC;CACX,GAAG,CAAC,CAAC;CAEL,MAAM,QAAQ,kBAAkB;EAC9B,cAAc,kBAAkB;EAChC,eAAe,EAAE;EACjB,QAAQ,CAAC;CACX,GAAG,CAAC,CAAC;CAOL,MAAM,EAAE,gBAAgB;CACxB,MAAM,SAAS,cAAc,QAAQ;CACrC,MAAM,cAAc,OAAO,WAAW;CACtC,MAAM,aAAa,cAAsC;EACvD,MAAM,WAAW,KAAK,OAAO,MAAM;EAOnC,OAAO,iBALL,aAAa,KAAA,IACT,WACA,gBAAgB,KAAA,IACd,KAAA,IACA,EAAE,QAAQ,CAAC,WAAW,EAAE,IACD,UAAU,OAAO,aAAa,KAAK,CAAC;CACrE,GAAG;EAAC;EAAK;EAAa;CAAM,CAAC;CAC7B,MAAM,gBAAgB,eAAe;EAAE;EAAU;CAAI,IAAI,CAAC,UAAU,GAAG,CAAC;CAExE,MAAM,SAAS,cACP,kBAAkB;EAAE;EAAO;EAAO;EAAQ;EAAY;CAAc,CAAC,GAC3E;EAAC;EAAO;EAAO;EAAQ;EAAY;CAAa,CAClD;CAIA,MAAM,UAAU,CAAC,eAAe,iBAAiB,SAAS,UAAU,OAAO,aAAa,KAAK,CAAC;CAE9F,OAAO,eACE;EACL,eAAe,eAAe,KAAA;EAC9B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;AACF"}
@@ -1,9 +1,10 @@
1
- const require_search_access = require("./search-access-Dugvttlg.cjs");
1
+ const require_relation_create = require("./relation-create-DDEYcKfw.cjs");
2
2
  let _burojs_core = require("@burojs/core");
3
3
  let _burojs_react = require("@burojs/react");
4
4
  let react = require("react");
5
5
  //#region src/fields/use-dialog-filter-state.ts
6
- function useDialogFilterState(resource, options) {
6
+ function useDialogFilterState(resourceInput, options) {
7
+ const resource = (0, _burojs_core.routeResourceKey)(resourceInput);
7
8
  const def = (0, _burojs_react.useApp)().registry.get(resource);
8
9
  const { available, fixed } = (0, _burojs_react.useFilterFields)(resource, {
9
10
  fixedFilters: options.fixedFilters,
@@ -30,7 +31,7 @@ function useDialogFilterState(resource, options) {
30
31
  const accessReady = access.status === "ready";
31
32
  const searchDecl = (0, react.useMemo)(() => {
32
33
  const declared = def?.views?.list?.search;
33
- return require_search_access.narrowSearchDecl(declared !== void 0 ? declared : searchField === void 0 ? void 0 : { fields: [searchField] }, (field) => access.fieldVisible(field));
34
+ return require_relation_create.narrowSearchDecl(declared !== void 0 ? declared : searchField === void 0 ? void 0 : { fields: [searchField] }, (field) => access.fieldVisible(field));
34
35
  }, [
35
36
  def,
36
37
  searchField,
@@ -53,7 +54,7 @@ function useDialogFilterState(resource, options) {
53
54
  searchDecl,
54
55
  searchContext
55
56
  ]);
56
- const blocked = !accessReady || require_search_access.namesHiddenField(filter, (field) => access.fieldVisible(field));
57
+ const blocked = !accessReady || require_relation_create.namesHiddenField(filter, (field) => access.fieldVisible(field));
57
58
  return (0, react.useMemo)(() => ({
58
59
  searchEnabled: searchDecl !== void 0,
59
60
  blocked,
@@ -89,4 +90,4 @@ Object.defineProperty(exports, "useDialogFilterState", {
89
90
  }
90
91
  });
91
92
 
92
- //# sourceMappingURL=use-dialog-filter-state-Dhe8PcB5.cjs.map
93
+ //# sourceMappingURL=use-dialog-filter-state-tNvqAAjh.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-dialog-filter-state-tNvqAAjh.cjs","names":["routeResourceKey","useApp","useFilterFields","useState","EMPTY_FILTER_GROUP","useCallback","useListAccess","useMemo","narrowSearchDecl","composeListFilter","namesHiddenField"],"sources":["../src/fields/use-dialog-filter-state.ts"],"sourcesContent":["import {\n EMPTY_FILTER_GROUP,\n composeListFilter,\n type ErasedResourceHandle,\n type FilterExpression,\n type FilterGroup,\n type FixedFilterInput,\n type ResolvedFixedFilter,\n type SearchDecl,\n routeResourceKey,\n type ResourceNameOrRef,\n} from '@burojs/core';\nimport { useApp, useFilterFields, useListAccess, type FilterFieldSpec } from '@burojs/react';\nimport { namesHiddenField, narrowSearchDecl } from './search-access.js';\nimport { useCallback, useMemo, useState } from 'react';\n\n/** The portable erased AST — the only shape this module deals in. */\ntype ErasedExpression = FilterExpression<ErasedResourceHandle>;\n\n/** Options for {@link useDialogFilterState}. */\nexport interface UseDialogFilterStateOptions {\n /** Call-site narrowing (e.g. scope the target list to a product group). */\n readonly fixedFilters?: readonly FixedFilterInput[];\n /**\n * Fallback search field, used ONLY when the target resource declares no\n * `views.list.search`. Wrapped into a single-field `SearchFieldsDecl`\n * (`{ fields: [searchField] }`, implicit `contains` — same degenerate form\n * `searchToFilter` already gives a bare field-name string) so the override\n * goes through the exact same `composeListFilter` → `searchToFilter` path\n * as a real declaration, rather than a second hand-built predicate at the\n * call site. The target's own declaration always wins when present — this\n * is a fallback, not an override of a declared search.\n */\n readonly searchField?: string;\n /**\n * Gates the field-derivation walk (`useFilterFields`'s `categorizeField`\n * pass over every field of the resource). Defaults to `true`. A caller\n * whose UI isn't visible yet (e.g. `RelationPickerDialog` before it's\n * opened) should pass `false` to skip that work on every mount /\n * `fixedFilters`-identity change; the derivation recomputes in full the\n * next render where this flips back to `true`.\n */\n readonly enabled?: boolean;\n}\n\n/** Return shape of {@link useDialogFilterState}. */\nexport interface DialogFilterState {\n readonly group: FilterGroup;\n readonly setGroup: (next: FilterGroup) => void;\n readonly search: string;\n readonly setSearch: (next: string) => void;\n readonly page: number;\n readonly setPage: (page: number) => void;\n readonly available: readonly FilterFieldSpec[];\n readonly fixed: readonly ResolvedFixedFilter[];\n /** The single expression to hand the data provider. */\n readonly filter: ErasedExpression | undefined;\n /** Whether the target has a search for this user (no field left means no search box). */\n readonly searchEnabled: boolean;\n /** The read must not be sent: undecided access, or a filter naming a field the user may not view. */\n readonly blocked: boolean;\n readonly reset: () => void;\n}\n\n/**\n * Local (non-URL) filter state for the relation-picker's advanced-search\n * dialog.\n *\n * `useResourceList` cannot serve this: it unconditionally calls\n * `useResourceUrlState`, which throws without a `RouterAdapterProvider` and,\n * when one IS present, writes to `?<resource>.*`. A picker dialog mounted\n * inside a form for resource A, searching TARGET resource B, would then\n * write B's list state into the URL — clobbering a real B list page open on\n * another tab of the same app, and reading back whatever that page last\n * wrote on open. So this hook keeps `group`/`search`/`page` in plain\n * `useState` and reuses the SAME composition core's list controller uses\n * (`composeListFilter`) so the two never drift on what a filter means.\n *\n * No `pinned` slot: pinned controls are a list-PAGE affordance (permanent\n * screen real estate for a couple of always-visible facets); a modal has no\n * spare real estate to spend on them.\n */\nexport function useDialogFilterState<const N extends string = string>(\n resourceInput: ResourceNameOrRef<N>,\n options: UseDialogFilterStateOptions,\n): DialogFilterState {\n const resource = routeResourceKey(resourceInput);\n const app = useApp();\n const def = app.registry.get(resource);\n const { available, fixed } = useFilterFields(resource, {\n fixedFilters: options.fixedFilters,\n enabled: options.enabled,\n });\n\n const [group, setGroupState] = useState<FilterGroup>(EMPTY_FILTER_GROUP);\n const [search, setSearchState] = useState('');\n const [page, setPage] = useState(1);\n\n // Any change to WHAT is filtered invalidates the current page — the same\n // rule `useResourceList`'s URL-state controller applies to its own\n // group/search setters.\n const setGroup = useCallback((next: FilterGroup) => {\n setGroupState(next);\n setPage(1);\n }, []);\n\n const setSearch = useCallback((next: string) => {\n setSearchState(next);\n setPage(1);\n }, []);\n\n const reset = useCallback(() => {\n setGroupState(EMPTY_FILTER_GROUP);\n setSearchState('');\n setPage(1);\n }, []);\n\n // The target's own declaration always wins; `options.searchField` only\n // fills in when the target declares no search at all. Memoized so an\n // omitted declaration doesn't hand `composeListFilter`'s `filter` memo a\n // fresh `{ fields: [...] }` literal (and therefore a spurious recompute)\n // on every render — see `UseDialogFilterStateOptions.searchField`'s doc.\n const { searchField } = options;\n const access = useListAccess(resource);\n const accessReady = access.status === 'ready';\n const searchDecl = useMemo<SearchDecl | undefined>(() => {\n const declared = def?.views?.list?.search;\n const base =\n declared !== undefined\n ? declared\n : searchField === undefined\n ? undefined\n : { fields: [searchField] };\n return narrowSearchDecl(base, (field) => access.fieldVisible(field));\n }, [def, searchField, access]);\n const searchContext = useMemo(() => ({ resource, def }), [resource, def]);\n\n const filter = useMemo<ErasedExpression | undefined>(\n () => composeListFilter({ fixed, group, search, searchDecl, searchContext }),\n [fixed, group, search, searchDecl, searchContext],\n );\n\n // A read whose composed filter names a field the user may not view is forbidden (an author\n // predicate, or a function search's output); an undecided access sends nothing yet.\n const blocked = !accessReady || namesHiddenField(filter, (field) => access.fieldVisible(field));\n\n return useMemo<DialogFilterState>(\n () => ({\n searchEnabled: searchDecl !== undefined,\n blocked,\n group,\n setGroup,\n search,\n setSearch,\n page,\n setPage,\n available,\n fixed,\n filter,\n reset,\n }),\n [\n group,\n setGroup,\n search,\n setSearch,\n page,\n available,\n fixed,\n filter,\n reset,\n searchDecl,\n blocked,\n ],\n );\n}\n"],"mappings":";;;;;AAkFA,SAAgB,qBACd,eACA,SACmB;CACnB,MAAM,YAAA,GAAWA,aAAAA,iBAAAA,CAAiB,aAAa;CAE/C,MAAM,OAAA,GADMC,cAAAA,OAAAA,CACE,CAAC,CAAC,SAAS,IAAI,QAAQ;CACrC,MAAM,EAAE,WAAW,WAAA,GAAUC,cAAAA,gBAAAA,CAAgB,UAAU;EACrD,cAAc,QAAQ;EACtB,SAAS,QAAQ;CACnB,CAAC;CAED,MAAM,CAAC,OAAO,kBAAA,GAAiBC,MAAAA,SAAAA,CAAsBC,aAAAA,kBAAkB;CACvE,MAAM,CAAC,QAAQ,mBAAA,GAAkBD,MAAAA,SAAAA,CAAS,EAAE;CAC5C,MAAM,CAAC,MAAM,YAAA,GAAWA,MAAAA,SAAAA,CAAS,CAAC;CAKlC,MAAM,YAAA,GAAWE,MAAAA,YAAAA,EAAa,SAAsB;EAClD,cAAc,IAAI;EAClB,QAAQ,CAAC;CACX,GAAG,CAAC,CAAC;CAEL,MAAM,aAAA,GAAYA,MAAAA,YAAAA,EAAa,SAAiB;EAC9C,eAAe,IAAI;EACnB,QAAQ,CAAC;CACX,GAAG,CAAC,CAAC;CAEL,MAAM,SAAA,GAAQA,MAAAA,YAAAA,OAAkB;EAC9B,cAAcD,aAAAA,kBAAkB;EAChC,eAAe,EAAE;EACjB,QAAQ,CAAC;CACX,GAAG,CAAC,CAAC;CAOL,MAAM,EAAE,gBAAgB;CACxB,MAAM,UAAA,GAASE,cAAAA,cAAAA,CAAc,QAAQ;CACrC,MAAM,cAAc,OAAO,WAAW;CACtC,MAAM,cAAA,GAAaC,MAAAA,QAAAA,OAAsC;EACvD,MAAM,WAAW,KAAK,OAAO,MAAM;EAOnC,OAAOC,wBAAAA,iBALL,aAAa,KAAA,IACT,WACA,gBAAgB,KAAA,IACd,KAAA,IACA,EAAE,QAAQ,CAAC,WAAW,EAAE,IACD,UAAU,OAAO,aAAa,KAAK,CAAC;CACrE,GAAG;EAAC;EAAK;EAAa;CAAM,CAAC;CAC7B,MAAM,iBAAA,GAAgBD,MAAAA,QAAAA,QAAe;EAAE;EAAU;CAAI,IAAI,CAAC,UAAU,GAAG,CAAC;CAExE,MAAM,UAAA,GAASA,MAAAA,QAAAA,QAAAA,GACPE,aAAAA,kBAAAA,CAAkB;EAAE;EAAO;EAAO;EAAQ;EAAY;CAAc,CAAC,GAC3E;EAAC;EAAO;EAAO;EAAQ;EAAY;CAAa,CAClD;CAIA,MAAM,UAAU,CAAC,eAAeC,wBAAAA,iBAAiB,SAAS,UAAU,OAAO,aAAa,KAAK,CAAC;CAE9F,QAAA,GAAOH,MAAAA,QAAAA,QACE;EACL,eAAe,eAAe,KAAA;EAC9B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burojs/ui-shadcn",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "license": "MIT",
5
5
  "description": "Buro: default shadcn+Tailwind UI kit with required testid attributes",
6
6
  "type": "module",
@@ -115,16 +115,16 @@
115
115
  "rehype-slug": "^6.0.0",
116
116
  "remark-directive": "^4",
117
117
  "remark-gfm": "^4",
118
- "sonner": "^1.7.0",
118
+ "sonner": "^2.0.8",
119
119
  "tailwind-merge": "^3.6.0",
120
120
  "tw-animate-css": "^1.4.0",
121
121
  "unist-util-visit": "^5",
122
122
  "zod": "^3.24.0",
123
- "@burojs/core": "0.6.0",
124
- "@burojs/field-types": "0.6.0",
125
- "@burojs/file-storage": "0.6.0",
126
- "@burojs/i18n-core": "0.6.0",
127
- "@burojs/react": "0.6.0"
123
+ "@burojs/core": "0.7.0",
124
+ "@burojs/field-types": "0.7.0",
125
+ "@burojs/file-storage": "0.7.0",
126
+ "@burojs/i18n-core": "0.7.0",
127
+ "@burojs/react": "0.7.0"
128
128
  },
129
129
  "peerDependencies": {
130
130
  "react": "^19.0.0",
@@ -147,9 +147,9 @@
147
147
  "react-dom": "^19.0.0",
148
148
  "typescript": "^5.5.0",
149
149
  "vitest": "^4.0.0",
150
- "@burojs/context": "0.6.0",
151
- "@burojs/export": "0.6.0",
152
- "@burojs/testing": "0.6.0",
150
+ "@burojs/context": "0.7.0",
151
+ "@burojs/export": "0.7.0",
152
+ "@burojs/testing": "0.7.0",
153
153
  "@burojs/tooling-eslint": "0.0.0",
154
154
  "@burojs/tooling-globals": "0.0.0",
155
155
  "@burojs/tooling-tsconfig": "0.0.0"
@@ -162,6 +162,6 @@
162
162
  "dev": "tsdown --watch",
163
163
  "test": "vitest run",
164
164
  "typecheck": "tsc --noEmit",
165
- "typecheck:tests": "tsc -p tsconfig.test.json"
165
+ "typecheck:tests": "tsc -p tsconfig.test.json && node tests/registration/run-cases.mjs"
166
166
  }
167
167
  }
@@ -1,108 +0,0 @@
1
- const require_button = require("./button-BxnnE28H.cjs");
2
- let _burojs_core = require("@burojs/core");
3
- let _burojs_react = require("@burojs/react");
4
- let react = require("react");
5
- let react_jsx_runtime = require("react/jsx-runtime");
6
- let _burojs_react_flows = require("@burojs/react/flows");
7
- //#region src/flows/bulk-summary.tsx
8
- const text = (t, key, fallback, params) => {
9
- const value = t(key, params === void 0 ? void 0 : { params });
10
- return value === key ? fallback : value;
11
- };
12
- function FlowBulkSummary({ resource, actionName, label, total, progress }) {
13
- const t = (0, _burojs_react.useOptionalT)();
14
- const caps = (0, _burojs_react.useI18nCaps)(resource);
15
- const forget = (0, _burojs_react_flows.useForgetEachAttempt)({
16
- resource,
17
- action: actionName
18
- });
19
- const [forgotten, setForgotten] = (0, react.useState)(() => new Set());
20
- const forgetText = text(t, "flow.forget", "I've checked — forget it");
21
- const testid = (0, _burojs_react.useTestIds)().bulkSummary(resource, actionName);
22
- const own = `resources.${resource}.actions.${actionName}.summary`;
23
- const params = {
24
- count: progress.success.length,
25
- total
26
- };
27
- const heading = caps.has(own) ? caps.t(own, { params }) : `${label}: ${text(t, "flow.bulkSummary", `${progress.success.length} processed`, params)} / ${total}`;
28
- const notProcessed = progress.rejected.filter((entry) => entry.reason !== "server");
29
- const refused = progress.rejected.filter((entry) => entry.reason === "server");
30
- const groups = [
31
- {
32
- key: "not-processed",
33
- title: text(t, "flow.rejected.precondition", "The record changed. Nothing was sent."),
34
- ids: notProcessed.map((entry) => entry.id)
35
- },
36
- {
37
- key: "rejected",
38
- title: text(t, "flow.rejected.validation", "The server refused the values."),
39
- ids: refused.map((entry) => entry.id)
40
- },
41
- {
42
- key: "failed",
43
- title: text(t, "flow.failed", "The request could not be sent."),
44
- ids: progress.failed
45
- },
46
- {
47
- key: "unknown",
48
- title: text(t, "flow.unknown", "It may have been applied. Nothing was resent."),
49
- ids: progress.unknown.filter((id) => !forgotten.has(id))
50
- },
51
- {
52
- key: "unresolved",
53
- title: text(t, "flow.bulkUnresolved", "Skipped: an earlier request for it may have been applied."),
54
- ids: progress.unresolved.filter((id) => !forgotten.has(id))
55
- },
56
- {
57
- key: "not-attempted",
58
- title: text(t, "flow.bulkNotAttempted", "Not attempted."),
59
- ids: progress.notAttempted
60
- }
61
- ];
62
- return (0, react_jsx_runtime.jsxs)("div", {
63
- "data-testid": testid,
64
- className: "flex flex-col gap-3 text-sm",
65
- children: [(0, react_jsx_runtime.jsx)("p", {
66
- "data-testid": `${testid}:heading`,
67
- className: "font-medium",
68
- children: heading
69
- }), groups.filter((group) => group.ids.length > 0).map((group) => (0, react_jsx_runtime.jsxs)("section", {
70
- "data-testid": `${testid}:${group.key}`,
71
- children: [(0, react_jsx_runtime.jsxs)("h3", {
72
- className: "font-medium",
73
- children: [
74
- group.title,
75
- " ",
76
- (0, react_jsx_runtime.jsxs)("span", {
77
- "data-testid": `${testid}:${group.key}:count`,
78
- className: "tabular-nums",
79
- children: [
80
- "(",
81
- group.ids.length,
82
- ")"
83
- ]
84
- })
85
- ]
86
- }), (0, react_jsx_runtime.jsxs)("ul", {
87
- className: "flex flex-wrap gap-1 text-muted-foreground",
88
- children: [group.ids.slice(0, _burojs_core.BULK_SUMMARY_IDS).map((id) => (0, react_jsx_runtime.jsxs)("li", {
89
- "data-testid": `${testid}:${group.key}:${id}`,
90
- className: "flex items-center gap-1",
91
- children: [id, group.key === "unknown" || group.key === "unresolved" ? (0, react_jsx_runtime.jsx)(require_button.Button, {
92
- type: "button",
93
- size: "sm",
94
- variant: "ghost",
95
- className: "h-6 px-1.5 text-xs",
96
- "data-testid": `${testid}:forget:${id}`,
97
- onClick: () => void forget(id).then(() => setForgotten((previous) => new Set([...previous, id]))),
98
- children: forgetText
99
- }) : null]
100
- }, id)), group.ids.length > _burojs_core.BULK_SUMMARY_IDS ? (0, react_jsx_runtime.jsxs)("li", { children: ["+", group.ids.length - _burojs_core.BULK_SUMMARY_IDS] }) : null]
101
- })]
102
- }, group.key))]
103
- });
104
- }
105
- //#endregion
106
- exports.FlowBulkSummary = FlowBulkSummary;
107
-
108
- //# sourceMappingURL=bulk-summary-CwSd6Yjb.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bulk-summary-CwSd6Yjb.cjs","names":["useOptionalT","useI18nCaps","useForgetEachAttempt","useState","useTestIds","BULK_SUMMARY_IDS","Button"],"sources":["../src/flows/bulk-summary.tsx"],"sourcesContent":["'use client';\nimport { BULK_SUMMARY_IDS, type BulkProgress } from '@burojs/core';\nimport { useI18nCaps, useOptionalT, useTestIds } from '@burojs/react';\nimport { useForgetEachAttempt } from '@burojs/react/flows';\nimport { type ReactElement, useState } from 'react';\nimport { Button } from '../primitives/button.js';\n\nexport interface FlowBulkSummaryProps {\n readonly resource: string;\n readonly actionName: string;\n /** The action's label. */\n readonly label: string;\n /** How many ids the run was given. */\n readonly total: number;\n readonly progress: BulkProgress;\n}\n\ntype Translate = ReturnType<typeof useOptionalT>;\nconst text = (t: Translate, key: string, fallback: string, params?: Record<string, unknown>) => {\n const value = t(key, params === undefined ? undefined : { params });\n return value === key ? fallback : value;\n};\n\n/**\n * A bulk flow's summary (P4 spec 4.16): the count that succeeded of the ids given (the flow's\n * own `resources.<r>.actions.<name>.summary`, `{{count}}` of `{{total}}`, else the framework's\n * count text), then one group per other outcome with its count and up to `BULK_SUMMARY_IDS` ids —\n * not processed (gone or its precondition failed), refused, failed, unknown (may have been\n * applied), unresolved from an earlier run, not attempted.\n */\nexport function FlowBulkSummary({\n resource,\n actionName,\n label,\n total,\n progress,\n}: FlowBulkSummaryProps): ReactElement {\n const t = useOptionalT();\n const caps = useI18nCaps(resource);\n // An id the run left unknown, or found unresolved, has its exit here (P4-C review I3): the user\n // checked the record, and the id's attempt is forgotten — the next run sends it again.\n const forget = useForgetEachAttempt({ resource, action: actionName });\n const [forgotten, setForgotten] = useState<ReadonlySet<string>>(() => new Set());\n const forgetText = text(t, 'flow.forget', \"I've checked — forget it\");\n const tid = useTestIds();\n const testid = tid.bulkSummary(resource, actionName);\n const own = `resources.${resource}.actions.${actionName}.summary`;\n const params = { count: progress.success.length, total };\n const heading = caps.has(own)\n ? caps.t(own, { params })\n : `${label}: ${text(t, 'flow.bulkSummary', `${progress.success.length} processed`, params)} / ${total}`;\n const notProcessed = progress.rejected.filter((entry) => entry.reason !== 'server');\n const refused = progress.rejected.filter((entry) => entry.reason === 'server');\n const groups: readonly { key: string; title: string; ids: readonly string[] }[] = [\n {\n key: 'not-processed',\n title: text(t, 'flow.rejected.precondition', 'The record changed. Nothing was sent.'),\n ids: notProcessed.map((entry) => entry.id),\n },\n {\n key: 'rejected',\n title: text(t, 'flow.rejected.validation', 'The server refused the values.'),\n ids: refused.map((entry) => entry.id),\n },\n {\n key: 'failed',\n title: text(t, 'flow.failed', 'The request could not be sent.'),\n ids: progress.failed,\n },\n {\n key: 'unknown',\n title: text(t, 'flow.unknown', 'It may have been applied. Nothing was resent.'),\n ids: progress.unknown.filter((id) => !forgotten.has(id)),\n },\n {\n key: 'unresolved',\n title: text(\n t,\n 'flow.bulkUnresolved',\n 'Skipped: an earlier request for it may have been applied.',\n ),\n ids: progress.unresolved.filter((id) => !forgotten.has(id)),\n },\n {\n key: 'not-attempted',\n title: text(t, 'flow.bulkNotAttempted', 'Not attempted.'),\n ids: progress.notAttempted,\n },\n ];\n return (\n <div data-testid={testid} className=\"flex flex-col gap-3 text-sm\">\n <p data-testid={`${testid}:heading`} className=\"font-medium\">\n {heading}\n </p>\n {groups\n .filter((group) => group.ids.length > 0)\n .map((group) => (\n <section key={group.key} data-testid={`${testid}:${group.key}`}>\n <h3 className=\"font-medium\">\n {group.title}{' '}\n <span data-testid={`${testid}:${group.key}:count`} className=\"tabular-nums\">\n ({group.ids.length})\n </span>\n </h3>\n <ul className=\"flex flex-wrap gap-1 text-muted-foreground\">\n {group.ids.slice(0, BULK_SUMMARY_IDS).map((id) => (\n <li\n key={id}\n data-testid={`${testid}:${group.key}:${id}`}\n className=\"flex items-center gap-1\"\n >\n {id}\n {group.key === 'unknown' || group.key === 'unresolved' ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-6 px-1.5 text-xs\"\n data-testid={`${testid}:forget:${id}`}\n onClick={() =>\n void forget(id).then(() =>\n setForgotten((previous) => new Set([...previous, id])),\n )\n }\n >\n {forgetText}\n </Button>\n ) : null}\n </li>\n ))}\n {group.ids.length > BULK_SUMMARY_IDS ? (\n <li>+{group.ids.length - BULK_SUMMARY_IDS}</li>\n ) : null}\n </ul>\n </section>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;;;AAkBA,MAAM,QAAQ,GAAc,KAAa,UAAkB,WAAqC;CAC9F,MAAM,QAAQ,EAAE,KAAK,WAAW,KAAA,IAAY,KAAA,IAAY,EAAE,OAAO,CAAC;CAClE,OAAO,UAAU,MAAM,WAAW;AACpC;AASA,SAAgB,gBAAgB,EAC9B,UACA,YACA,OACA,OACA,YACqC;CACrC,MAAM,KAAA,GAAIA,cAAAA,aAAAA,CAAa;CACvB,MAAM,QAAA,GAAOC,cAAAA,YAAAA,CAAY,QAAQ;CAGjC,MAAM,UAAA,GAASC,oBAAAA,qBAAAA,CAAqB;EAAE;EAAU,QAAQ;CAAW,CAAC;CACpE,MAAM,CAAC,WAAW,iBAAA,GAAgBC,MAAAA,SAAAA,OAAoC,IAAI,IAAI,CAAC;CAC/E,MAAM,aAAa,KAAK,GAAG,eAAe,0BAA0B;CAEpE,MAAM,UAAA,GADMC,cAAAA,WAAAA,CACK,CAAC,CAAC,YAAY,UAAU,UAAU;CACnD,MAAM,MAAM,aAAa,SAAS,WAAW,WAAW;CACxD,MAAM,SAAS;EAAE,OAAO,SAAS,QAAQ;EAAQ;CAAM;CACvD,MAAM,UAAU,KAAK,IAAI,GAAG,IACxB,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,IACtB,GAAG,MAAM,IAAI,KAAK,GAAG,oBAAoB,GAAG,SAAS,QAAQ,OAAO,aAAa,MAAM,EAAE,KAAK;CAClG,MAAM,eAAe,SAAS,SAAS,QAAQ,UAAU,MAAM,WAAW,QAAQ;CAClF,MAAM,UAAU,SAAS,SAAS,QAAQ,UAAU,MAAM,WAAW,QAAQ;CAC7E,MAAM,SAA4E;EAChF;GACE,KAAK;GACL,OAAO,KAAK,GAAG,8BAA8B,uCAAuC;GACpF,KAAK,aAAa,KAAK,UAAU,MAAM,EAAE;EAC3C;EACA;GACE,KAAK;GACL,OAAO,KAAK,GAAG,4BAA4B,gCAAgC;GAC3E,KAAK,QAAQ,KAAK,UAAU,MAAM,EAAE;EACtC;EACA;GACE,KAAK;GACL,OAAO,KAAK,GAAG,eAAe,gCAAgC;GAC9D,KAAK,SAAS;EAChB;EACA;GACE,KAAK;GACL,OAAO,KAAK,GAAG,gBAAgB,+CAA+C;GAC9E,KAAK,SAAS,QAAQ,QAAQ,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC;EACzD;EACA;GACE,KAAK;GACL,OAAO,KACL,GACA,uBACA,2DACF;GACA,KAAK,SAAS,WAAW,QAAQ,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC;EAC5D;EACA;GACE,KAAK;GACL,OAAO,KAAK,GAAG,yBAAyB,gBAAgB;GACxD,KAAK,SAAS;EAChB;CACF;CACA,QACE,GAAA,kBAAA,KAAA,CAAC,OAAD;EAAK,eAAa;EAAQ,WAAU;EAApC,UAAA,EACE,GAAA,kBAAA,IAAA,CAAC,KAAD;GAAG,eAAa,GAAG,OAAO;GAAW,WAAU;GAC5C,UAAA;EACA,CAAA,GACF,OACE,QAAQ,UAAU,MAAM,IAAI,SAAS,CAAC,CAAC,CACvC,KAAK,WACJ,GAAA,kBAAA,KAAA,CAAC,WAAD;GAAyB,eAAa,GAAG,OAAO,GAAG,MAAM;GAAzD,UAAA,EACE,GAAA,kBAAA,KAAA,CAAC,MAAD;IAAI,WAAU;IAAd,UAAA;KACG,MAAM;KAAO;MACd,GAAA,kBAAA,KAAA,CAAC,QAAD;MAAM,eAAa,GAAG,OAAO,GAAG,MAAM,IAAI;MAAS,WAAU;MAA7D,UAAA;OAA4E;OACxE,MAAM,IAAI;OAAO;MACf;;IACJ;GACJ,CAAA,IAAA,GAAA,kBAAA,KAAA,CAAC,MAAD;IAAI,WAAU;IAAd,UAAA,CACG,MAAM,IAAI,MAAM,GAAGC,aAAAA,gBAAgB,CAAC,CAAC,KAAK,QACzC,GAAA,kBAAA,KAAA,CAAC,MAAD;KAEE,eAAa,GAAG,OAAO,GAAG,MAAM,IAAI,GAAG;KACvC,WAAU;KAHZ,UAAA,CAKG,IACA,MAAM,QAAQ,aAAa,MAAM,QAAQ,gBACxC,GAAA,kBAAA,IAAA,CAACC,eAAAA,QAAD;MACE,MAAK;MACL,MAAK;MACL,SAAQ;MACR,WAAU;MACV,eAAa,GAAG,OAAO,UAAU;MACjC,eACE,KAAK,OAAO,EAAE,CAAC,CAAC,WACd,cAAc,aAAa,IAAI,IAAI,CAAC,GAAG,UAAU,EAAE,CAAC,CAAC,CACvD;MAGD,UAAA;KACK,CAAA,IACN,IACF;IArBG,GAAA,EAqBH,CACL,GACA,MAAM,IAAI,SAASD,aAAAA,oBAClB,GAAA,kBAAA,KAAA,CAAC,MAAD,EAAA,UAAA,CAAI,KAAE,MAAM,IAAI,SAASA,aAAAA,gBAAqB,EAAA,CAAA,IAC5C,IACF;GACG,CAAA,CAAA;EArCK,GAAA,MAAM,GAqCX,CACV,CACA;;AAET"}
@@ -1,108 +0,0 @@
1
- import { t as Button } from "./button-MWVeZaqj.js";
2
- import { BULK_SUMMARY_IDS } from "@burojs/core";
3
- import { useI18nCaps, useOptionalT, useTestIds } from "@burojs/react";
4
- import { useState } from "react";
5
- import { jsx, jsxs } from "react/jsx-runtime";
6
- import { useForgetEachAttempt } from "@burojs/react/flows";
7
- //#region src/flows/bulk-summary.tsx
8
- const text = (t, key, fallback, params) => {
9
- const value = t(key, params === void 0 ? void 0 : { params });
10
- return value === key ? fallback : value;
11
- };
12
- function FlowBulkSummary({ resource, actionName, label, total, progress }) {
13
- const t = useOptionalT();
14
- const caps = useI18nCaps(resource);
15
- const forget = useForgetEachAttempt({
16
- resource,
17
- action: actionName
18
- });
19
- const [forgotten, setForgotten] = useState(() => new Set());
20
- const forgetText = text(t, "flow.forget", "I've checked — forget it");
21
- const testid = useTestIds().bulkSummary(resource, actionName);
22
- const own = `resources.${resource}.actions.${actionName}.summary`;
23
- const params = {
24
- count: progress.success.length,
25
- total
26
- };
27
- const heading = caps.has(own) ? caps.t(own, { params }) : `${label}: ${text(t, "flow.bulkSummary", `${progress.success.length} processed`, params)} / ${total}`;
28
- const notProcessed = progress.rejected.filter((entry) => entry.reason !== "server");
29
- const refused = progress.rejected.filter((entry) => entry.reason === "server");
30
- const groups = [
31
- {
32
- key: "not-processed",
33
- title: text(t, "flow.rejected.precondition", "The record changed. Nothing was sent."),
34
- ids: notProcessed.map((entry) => entry.id)
35
- },
36
- {
37
- key: "rejected",
38
- title: text(t, "flow.rejected.validation", "The server refused the values."),
39
- ids: refused.map((entry) => entry.id)
40
- },
41
- {
42
- key: "failed",
43
- title: text(t, "flow.failed", "The request could not be sent."),
44
- ids: progress.failed
45
- },
46
- {
47
- key: "unknown",
48
- title: text(t, "flow.unknown", "It may have been applied. Nothing was resent."),
49
- ids: progress.unknown.filter((id) => !forgotten.has(id))
50
- },
51
- {
52
- key: "unresolved",
53
- title: text(t, "flow.bulkUnresolved", "Skipped: an earlier request for it may have been applied."),
54
- ids: progress.unresolved.filter((id) => !forgotten.has(id))
55
- },
56
- {
57
- key: "not-attempted",
58
- title: text(t, "flow.bulkNotAttempted", "Not attempted."),
59
- ids: progress.notAttempted
60
- }
61
- ];
62
- return jsxs("div", {
63
- "data-testid": testid,
64
- className: "flex flex-col gap-3 text-sm",
65
- children: [jsx("p", {
66
- "data-testid": `${testid}:heading`,
67
- className: "font-medium",
68
- children: heading
69
- }), groups.filter((group) => group.ids.length > 0).map((group) => jsxs("section", {
70
- "data-testid": `${testid}:${group.key}`,
71
- children: [jsxs("h3", {
72
- className: "font-medium",
73
- children: [
74
- group.title,
75
- " ",
76
- jsxs("span", {
77
- "data-testid": `${testid}:${group.key}:count`,
78
- className: "tabular-nums",
79
- children: [
80
- "(",
81
- group.ids.length,
82
- ")"
83
- ]
84
- })
85
- ]
86
- }), jsxs("ul", {
87
- className: "flex flex-wrap gap-1 text-muted-foreground",
88
- children: [group.ids.slice(0, BULK_SUMMARY_IDS).map((id) => jsxs("li", {
89
- "data-testid": `${testid}:${group.key}:${id}`,
90
- className: "flex items-center gap-1",
91
- children: [id, group.key === "unknown" || group.key === "unresolved" ? jsx(Button, {
92
- type: "button",
93
- size: "sm",
94
- variant: "ghost",
95
- className: "h-6 px-1.5 text-xs",
96
- "data-testid": `${testid}:forget:${id}`,
97
- onClick: () => void forget(id).then(() => setForgotten((previous) => new Set([...previous, id]))),
98
- children: forgetText
99
- }) : null]
100
- }, id)), group.ids.length > BULK_SUMMARY_IDS ? jsxs("li", { children: ["+", group.ids.length - BULK_SUMMARY_IDS] }) : null]
101
- })]
102
- }, group.key))]
103
- });
104
- }
105
- //#endregion
106
- export { FlowBulkSummary };
107
-
108
- //# sourceMappingURL=bulk-summary-GxMDP4bw.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bulk-summary-GxMDP4bw.js","names":[],"sources":["../src/flows/bulk-summary.tsx"],"sourcesContent":["'use client';\nimport { BULK_SUMMARY_IDS, type BulkProgress } from '@burojs/core';\nimport { useI18nCaps, useOptionalT, useTestIds } from '@burojs/react';\nimport { useForgetEachAttempt } from '@burojs/react/flows';\nimport { type ReactElement, useState } from 'react';\nimport { Button } from '../primitives/button.js';\n\nexport interface FlowBulkSummaryProps {\n readonly resource: string;\n readonly actionName: string;\n /** The action's label. */\n readonly label: string;\n /** How many ids the run was given. */\n readonly total: number;\n readonly progress: BulkProgress;\n}\n\ntype Translate = ReturnType<typeof useOptionalT>;\nconst text = (t: Translate, key: string, fallback: string, params?: Record<string, unknown>) => {\n const value = t(key, params === undefined ? undefined : { params });\n return value === key ? fallback : value;\n};\n\n/**\n * A bulk flow's summary (P4 spec 4.16): the count that succeeded of the ids given (the flow's\n * own `resources.<r>.actions.<name>.summary`, `{{count}}` of `{{total}}`, else the framework's\n * count text), then one group per other outcome with its count and up to `BULK_SUMMARY_IDS` ids —\n * not processed (gone or its precondition failed), refused, failed, unknown (may have been\n * applied), unresolved from an earlier run, not attempted.\n */\nexport function FlowBulkSummary({\n resource,\n actionName,\n label,\n total,\n progress,\n}: FlowBulkSummaryProps): ReactElement {\n const t = useOptionalT();\n const caps = useI18nCaps(resource);\n // An id the run left unknown, or found unresolved, has its exit here (P4-C review I3): the user\n // checked the record, and the id's attempt is forgotten — the next run sends it again.\n const forget = useForgetEachAttempt({ resource, action: actionName });\n const [forgotten, setForgotten] = useState<ReadonlySet<string>>(() => new Set());\n const forgetText = text(t, 'flow.forget', \"I've checked — forget it\");\n const tid = useTestIds();\n const testid = tid.bulkSummary(resource, actionName);\n const own = `resources.${resource}.actions.${actionName}.summary`;\n const params = { count: progress.success.length, total };\n const heading = caps.has(own)\n ? caps.t(own, { params })\n : `${label}: ${text(t, 'flow.bulkSummary', `${progress.success.length} processed`, params)} / ${total}`;\n const notProcessed = progress.rejected.filter((entry) => entry.reason !== 'server');\n const refused = progress.rejected.filter((entry) => entry.reason === 'server');\n const groups: readonly { key: string; title: string; ids: readonly string[] }[] = [\n {\n key: 'not-processed',\n title: text(t, 'flow.rejected.precondition', 'The record changed. Nothing was sent.'),\n ids: notProcessed.map((entry) => entry.id),\n },\n {\n key: 'rejected',\n title: text(t, 'flow.rejected.validation', 'The server refused the values.'),\n ids: refused.map((entry) => entry.id),\n },\n {\n key: 'failed',\n title: text(t, 'flow.failed', 'The request could not be sent.'),\n ids: progress.failed,\n },\n {\n key: 'unknown',\n title: text(t, 'flow.unknown', 'It may have been applied. Nothing was resent.'),\n ids: progress.unknown.filter((id) => !forgotten.has(id)),\n },\n {\n key: 'unresolved',\n title: text(\n t,\n 'flow.bulkUnresolved',\n 'Skipped: an earlier request for it may have been applied.',\n ),\n ids: progress.unresolved.filter((id) => !forgotten.has(id)),\n },\n {\n key: 'not-attempted',\n title: text(t, 'flow.bulkNotAttempted', 'Not attempted.'),\n ids: progress.notAttempted,\n },\n ];\n return (\n <div data-testid={testid} className=\"flex flex-col gap-3 text-sm\">\n <p data-testid={`${testid}:heading`} className=\"font-medium\">\n {heading}\n </p>\n {groups\n .filter((group) => group.ids.length > 0)\n .map((group) => (\n <section key={group.key} data-testid={`${testid}:${group.key}`}>\n <h3 className=\"font-medium\">\n {group.title}{' '}\n <span data-testid={`${testid}:${group.key}:count`} className=\"tabular-nums\">\n ({group.ids.length})\n </span>\n </h3>\n <ul className=\"flex flex-wrap gap-1 text-muted-foreground\">\n {group.ids.slice(0, BULK_SUMMARY_IDS).map((id) => (\n <li\n key={id}\n data-testid={`${testid}:${group.key}:${id}`}\n className=\"flex items-center gap-1\"\n >\n {id}\n {group.key === 'unknown' || group.key === 'unresolved' ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-6 px-1.5 text-xs\"\n data-testid={`${testid}:forget:${id}`}\n onClick={() =>\n void forget(id).then(() =>\n setForgotten((previous) => new Set([...previous, id])),\n )\n }\n >\n {forgetText}\n </Button>\n ) : null}\n </li>\n ))}\n {group.ids.length > BULK_SUMMARY_IDS ? (\n <li>+{group.ids.length - BULK_SUMMARY_IDS}</li>\n ) : null}\n </ul>\n </section>\n ))}\n </div>\n );\n}\n"],"mappings":"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"}