@burojs/ui-shadcn 0.4.0 → 0.5.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.
- package/README.md +22 -5
- package/dist/bulk-summary-CwSd6Yjb.cjs +108 -0
- package/dist/bulk-summary-CwSd6Yjb.cjs.map +1 -0
- package/dist/bulk-summary-GxMDP4bw.js +108 -0
- package/dist/bulk-summary-GxMDP4bw.js.map +1 -0
- package/dist/button-BxnnE28H.cjs +55 -0
- package/dist/button-BxnnE28H.cjs.map +1 -0
- package/dist/button-MWVeZaqj.js +44 -0
- package/dist/button-MWVeZaqj.js.map +1 -0
- package/dist/candidates-gate-CX2PBu6l.js +63 -0
- package/dist/candidates-gate-CX2PBu6l.js.map +1 -0
- package/dist/candidates-gate-DiFI52r7.cjs +70 -0
- package/dist/candidates-gate-DiFI52r7.cjs.map +1 -0
- package/dist/candidates-relation-picker-CBsVcnPR.js +82 -0
- package/dist/candidates-relation-picker-CBsVcnPR.js.map +1 -0
- package/dist/candidates-relation-picker-Dd_GVLlu.cjs +84 -0
- package/dist/candidates-relation-picker-Dd_GVLlu.cjs.map +1 -0
- package/dist/check-confirm-dialog-BRByTFpg.cjs +1534 -0
- package/dist/check-confirm-dialog-BRByTFpg.cjs.map +1 -0
- package/dist/check-confirm-dialog-CkaVTPT9.js +1232 -0
- package/dist/check-confirm-dialog-CkaVTPT9.js.map +1 -0
- package/dist/dialog-B_rt9S1c.js +63 -0
- package/dist/dialog-B_rt9S1c.js.map +1 -0
- package/dist/dialog-DRNUq9EA.cjs +112 -0
- package/dist/dialog-DRNUq9EA.cjs.map +1 -0
- package/dist/filter-builder-B3mZFL-K.cjs +1761 -0
- package/dist/filter-builder-B3mZFL-K.cjs.map +1 -0
- package/dist/filter-builder-DOnaXNfP.js +1605 -0
- package/dist/filter-builder-DOnaXNfP.js.map +1 -0
- package/dist/flow-runtime-B6zOHYqI.cjs +255 -0
- package/dist/flow-runtime-B6zOHYqI.cjs.map +1 -0
- package/dist/flow-runtime-D8h99FSg.js +252 -0
- package/dist/flow-runtime-D8h99FSg.js.map +1 -0
- package/dist/flow-screen-CX05b3G9.cjs +705 -0
- package/dist/flow-screen-CX05b3G9.cjs.map +1 -0
- package/dist/flow-screen-CmbARSqg.js +701 -0
- package/dist/flow-screen-CmbARSqg.js.map +1 -0
- package/dist/index.cjs +2825 -11912
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1612 -1862
- package/dist/index.d.ts +1700 -1950
- package/dist/index.js +2498 -11605
- package/dist/index.js.map +1 -1
- package/dist/inline-flow-host-B7U1OJUe.cjs +112 -0
- package/dist/inline-flow-host-B7U1OJUe.cjs.map +1 -0
- package/dist/inline-flow-host-C3H2UpSA.js +112 -0
- package/dist/inline-flow-host-C3H2UpSA.js.map +1 -0
- package/dist/input-BI0czGyI.cjs +23 -0
- package/dist/input-BI0czGyI.cjs.map +1 -0
- package/dist/input-CUPStzZO.js +17 -0
- package/dist/input-CUPStzZO.js.map +1 -0
- package/dist/label-66W1qbke.js +29 -0
- package/dist/label-66W1qbke.js.map +1 -0
- package/dist/label-w6JYf2cF.cjs +36 -0
- package/dist/label-w6JYf2cF.cjs.map +1 -0
- package/dist/lazy-relation-picker-dialog-2oxnA6IR.js +14 -0
- package/dist/lazy-relation-picker-dialog-2oxnA6IR.js.map +1 -0
- package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs +19 -0
- package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs.map +1 -0
- package/dist/markdown/index.cjs +1 -1
- package/dist/markdown/index.js +1 -1
- package/dist/page-flow-route-CQ7HsbBm.cjs +277 -0
- package/dist/page-flow-route-CQ7HsbBm.cjs.map +1 -0
- package/dist/page-flow-route-CyXSvXSE.js +277 -0
- package/dist/page-flow-route-CyXSvXSE.js.map +1 -0
- package/dist/peek-filter-CnqHwTMU.cjs +51 -0
- package/dist/peek-filter-CnqHwTMU.cjs.map +1 -0
- package/dist/peek-filter-ErLfO153.js +28 -0
- package/dist/peek-filter-ErLfO153.js.map +1 -0
- package/dist/place-node--GdsP688.js +7315 -0
- package/dist/place-node--GdsP688.js.map +1 -0
- package/dist/place-node-SIp4XYQH.cjs +7833 -0
- package/dist/place-node-SIp4XYQH.cjs.map +1 -0
- package/dist/related-list-cards-B6x9HQQp.js +42 -0
- package/dist/related-list-cards-B6x9HQQp.js.map +1 -0
- package/dist/related-list-cards-nxuODlIA.cjs +42 -0
- package/dist/related-list-cards-nxuODlIA.cjs.map +1 -0
- package/dist/related-list-timeline-BYJKEbmA.cjs +142 -0
- package/dist/related-list-timeline-BYJKEbmA.cjs.map +1 -0
- package/dist/related-list-timeline-DEdfd-rr.js +142 -0
- package/dist/related-list-timeline-DEdfd-rr.js.map +1 -0
- package/dist/related-rows-B4levHcR.js +186 -0
- package/dist/related-rows-B4levHcR.js.map +1 -0
- package/dist/related-rows-zh5f_4QR.cjs +209 -0
- package/dist/related-rows-zh5f_4QR.cjs.map +1 -0
- package/dist/relation-create-BOaUe12G.cjs +362 -0
- package/dist/relation-create-BOaUe12G.cjs.map +1 -0
- package/dist/relation-create-Bg-eEJLs.js +337 -0
- package/dist/relation-create-Bg-eEJLs.js.map +1 -0
- package/dist/relation-many-picker-B-NCEUgX.cjs +465 -0
- package/dist/relation-many-picker-B-NCEUgX.cjs.map +1 -0
- package/dist/relation-many-picker-gdAOD-qK.js +464 -0
- package/dist/relation-many-picker-gdAOD-qK.js.map +1 -0
- package/dist/relation-picker-BAY77ivO.js +462 -0
- package/dist/relation-picker-BAY77ivO.js.map +1 -0
- package/dist/relation-picker-Da_uhFvA.cjs +479 -0
- package/dist/relation-picker-Da_uhFvA.cjs.map +1 -0
- package/dist/relation-picker-dialog-M0pQaWlp.cjs +300 -0
- package/dist/relation-picker-dialog-M0pQaWlp.cjs.map +1 -0
- package/dist/relation-picker-dialog-hA9rYrZB.js +299 -0
- package/dist/relation-picker-dialog-hA9rYrZB.js.map +1 -0
- package/dist/resource-page-router-OSQyRA5b.cjs +1779 -0
- package/dist/resource-page-router-OSQyRA5b.cjs.map +1 -0
- package/dist/resource-page-router-T4vS5gbJ.js +1582 -0
- package/dist/resource-page-router-T4vS5gbJ.js.map +1 -0
- package/dist/{rich-text-IDFvyvRE.js → rich-text-DFerywgz.js} +7 -4
- package/dist/{rich-text-IDFvyvRE.js.map → rich-text-DFerywgz.js.map} +1 -1
- package/dist/{rich-text-lndbYqzR.cjs → rich-text-vTyVjDoa.cjs} +25 -22
- package/dist/{rich-text-lndbYqzR.cjs.map → rich-text-vTyVjDoa.cjs.map} +1 -1
- package/dist/search-access-B7G5oR8n.cjs +812 -0
- package/dist/search-access-B7G5oR8n.cjs.map +1 -0
- package/dist/search-access-CURsWn-U.js +669 -0
- package/dist/search-access-CURsWn-U.js.map +1 -0
- package/dist/shell-BPlk8hEa.js +205 -0
- package/dist/shell-BPlk8hEa.js.map +1 -0
- package/dist/shell-DxhlsPwm.cjs +271 -0
- package/dist/shell-DxhlsPwm.cjs.map +1 -0
- package/dist/skeleton-U2noefj5.js +13 -0
- package/dist/skeleton-U2noefj5.js.map +1 -0
- package/dist/skeleton-_1gP7wan.cjs +18 -0
- package/dist/skeleton-_1gP7wan.cjs.map +1 -0
- package/dist/styles.css +241 -9
- package/dist/toaster-C7CiYA4Z.cjs +248 -0
- package/dist/toaster-C7CiYA4Z.cjs.map +1 -0
- package/dist/toaster-CfCRLgR-.js +164 -0
- package/dist/toaster-CfCRLgR-.js.map +1 -0
- package/dist/use-dialog-filter-state-BBCC0OEK.cjs +92 -0
- package/dist/use-dialog-filter-state-BBCC0OEK.cjs.map +1 -0
- package/dist/use-dialog-filter-state-BDcgS5Oz.js +87 -0
- package/dist/use-dialog-filter-state-BDcgS5Oz.js.map +1 -0
- package/dist/{use-theme-BwPWinB2.cjs → use-theme-DFlpXils.cjs} +12 -1
- package/dist/use-theme-DFlpXils.cjs.map +1 -0
- package/dist/{use-theme-IHmtrhEt.js → use-theme-dnE3Od3I.js} +7 -2
- package/dist/use-theme-dnE3Od3I.js.map +1 -0
- package/package.json +16 -14
- package/dist/use-theme-BwPWinB2.cjs.map +0 -1
- package/dist/use-theme-IHmtrhEt.js.map +0 -1
package/README.md
CHANGED
|
@@ -51,15 +51,32 @@ are the working examples to copy from. (This line named `apps/playground` as
|
|
|
51
51
|
the Next example. It was wrong twice over: that application had been a Vite
|
|
52
52
|
app since 2026-07-31, and SP9 has now deleted it.)
|
|
53
53
|
|
|
54
|
+
## Router setup
|
|
55
|
+
|
|
56
|
+
Mount your router's `RouterAdapterProvider` (`@burojs/react`) above
|
|
57
|
+
`<BuroProvider>`: it is the router's only binding, and every route match, active
|
|
58
|
+
nav item, breadcrumb and framework navigation goes through it. Then give
|
|
59
|
+
`<LinkComponentProvider linkComponent={…}>` your router's link component, which
|
|
60
|
+
only renders anchors. `RouterAdapterProvider`'s docblock has the react-router and
|
|
61
|
+
Next App Router adapters; `examples/vite` and `examples/next` mount them.
|
|
62
|
+
|
|
63
|
+
## App shell
|
|
64
|
+
|
|
65
|
+
**→ [docs/app-shell-guide.md](../../docs/app-shell-guide.md)** covers
|
|
66
|
+
`AppShell`, the ready-made application chrome, and how to replace any part of it.
|
|
67
|
+
|
|
54
68
|
## What's included
|
|
55
69
|
|
|
56
|
-
- Layout: AppLayout, Sidebar, Breadcrumbs
|
|
70
|
+
- Layout: AppLayout, AppSidebar and AppHeader (each slot you omit gets its default — brand, `<NavMain />` and `<UserMenu />`; `<HeaderBreadcrumbs />`, the palette chip, app actions and refresh — and `false` empties it), Sidebar, Breadcrumbs, HeaderBreadcrumbs (the header trail of the current route)
|
|
57
71
|
- Auth: LoginForm
|
|
58
|
-
- Resource views: ResourceList,
|
|
72
|
+
- Resource views: ResourceList, and `<RecordScreen resource mode recordId? …>` — a record's show, edit or create screen outside a route, in the built-in frame or in your own layout of its pieces (`RecordTitle`, `RecordActions`, `CopyId`, `FormActions`)
|
|
73
|
+
- Record pages: `<RecordPage resource action />` (plus `defaults` on `action="create"`) — show, edit and create wired to data (`useOne`, `useUpdateOne`, `useCreateOne`), with translated loading / not-found / session-expired / error states and the open panel of a keyed tabs node in the URL (`?<tabsKey>=<panelKey>`); `<ResourcePageRouter>` / `<NavAreaPage>` render it for every record route (a different layout is a frame on the screen, `views.<mode>.frame`; record overrides were removed in P6); the page draws no breadcrumb trail of its own: `<HeaderBreadcrumbs />` in `<AppHeader breadcrumbs>` draws the record trail there (`<RecordBreadcrumbs />` standalone), with the record's title once the page has loaded it and the id while it loads or has failed, and on a page-hosted flow's route ending with the flow's label; each show and edit visit is recorded under the signed-in user for the command palette's Recent group
|
|
74
|
+
- Custom pages: a page declared with a `body` (`defineCustomPage({ …, body: (l) => [l.place(block, { key })] })`) renders on its route under its label as the `<h1>`, behind the page's and its groups' `visible`; a page without `body` is a nav entry over a route the host owns
|
|
59
75
|
- Field renderers: text, number, boolean, date, enum, textarea (display + form input)
|
|
60
|
-
- Actions: ActionButton, BulkActionToolbar (
|
|
61
|
-
- Notifications: Toaster
|
|
62
|
-
-
|
|
76
|
+
- Actions: ActionButton (links, modals and flows), FlowActionButton, AppFlowButton, BulkActionToolbar (bulk flows); `useAction` from `@burojs/react/flows` runs a flow from your own control
|
|
77
|
+
- Notifications: Toaster (Sonner) — render it inside `<BuroProvider>`, where it registers itself as the dispatcher's notification sink
|
|
78
|
+
- Unsaved-changes dialog: `<NavigationGuardDialog>`, registered by `<AppLayout>` (the guard itself is `<BuroProvider>`'s)
|
|
79
|
+
- UI presentation hosts: dialog/sheet chrome for the framework-mounted `<UIPresentations/>` overlay stack (`shadcnPresentationHosts`), backing `modal` actions and the flow host (`screens.flow`, the `buro:flow` dialog or sheet)
|
|
63
80
|
- Vendored shadcn primitives: Button, Input, Label, Form, Table, Card, Dialog, Sheet, Select, Checkbox, Textarea, Badge
|
|
64
81
|
|
|
65
82
|
## License
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const require_use_theme = require("./use-theme-DFlpXils.cjs");
|
|
2
|
+
let react = require("react");
|
|
3
|
+
react = require_use_theme.__toESM(react, 1);
|
|
4
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
let _radix_ui_react_slot = require("@radix-ui/react-slot");
|
|
6
|
+
//#region src/primitives/button.tsx
|
|
7
|
+
const buttonVariants = (0, require("class-variance-authority").cva)("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 disabled:saturate-0 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:saturate-0 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", {
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
11
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
12
|
+
outline: "border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground",
|
|
13
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
14
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
15
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
16
|
+
},
|
|
17
|
+
size: {
|
|
18
|
+
default: "h-9 px-4 py-2",
|
|
19
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
20
|
+
lg: "h-10 rounded-md px-6",
|
|
21
|
+
icon: "h-9 w-9"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "default",
|
|
26
|
+
size: "default"
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const Button = react.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
30
|
+
return (0, react_jsx_runtime.jsx)(asChild ? _radix_ui_react_slot.Slot : "button", {
|
|
31
|
+
className: require_use_theme.cn(buttonVariants({
|
|
32
|
+
variant,
|
|
33
|
+
size,
|
|
34
|
+
className
|
|
35
|
+
})),
|
|
36
|
+
ref,
|
|
37
|
+
...props
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
Button.displayName = "Button";
|
|
41
|
+
//#endregion
|
|
42
|
+
Object.defineProperty(exports, "Button", {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
get: function() {
|
|
45
|
+
return Button;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
Object.defineProperty(exports, "buttonVariants", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
get: function() {
|
|
51
|
+
return buttonVariants;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=button-BxnnE28H.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button-BxnnE28H.cjs","names":["cva","React","Slot","cn"],"sources":["../src/primitives/button.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn.js';\n\nconst buttonVariants = cva(\n 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 disabled:saturate-0 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:saturate-0 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n outline:\n 'border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground',\n secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n // Shadcn new-york current defaults. The shared stylesheet keeps the\n // standard 16px root; editable controls choose responsive typography\n // locally when they need mobile `text-base` and desktop `md:text-sm`.\n default: 'h-9 px-4 py-2',\n sm: 'h-8 rounded-md px-3 text-xs',\n lg: 'h-10 rounded-md px-6',\n icon: 'h-9 w-9',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n return (\n <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />\n );\n },\n);\nButton.displayName = 'Button';\n\nexport { Button, buttonVariants };\n"],"mappings":";;;;;;AAOA,MAAM,kBAAA,qCAAiBA,CAAAA,CAAAA,IAAAA,CACrB,ieACA;CACE,UAAU;EACR,SAAS;GACP,SAAS;GACT,aAAa;GACb,SACE;GACF,WAAW;GACX,OAAO;GACP,MAAM;EACR;EACA,MAAM;GAIJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,MAAM;EACR;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;AAQA,MAAM,SAASC,MAAM,YAClB,EAAE,WAAW,SAAS,MAAM,UAAU,OAAO,GAAG,SAAS,QAAQ;CAEhE,QACE,GAAA,kBAAA,IAAA,CAFW,UAAUC,qBAAAA,OAAO,UAE5B;EAAM,WAAWC,kBAAAA,GAAG,eAAe;GAAE;GAAS;GAAM;EAAU,CAAC,CAAC;EAAQ;EAAK,GAAI;CAAQ,CAAA;AAE7F,CACF;AACA,OAAO,cAAc"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { l as cn } from "./use-theme-dnE3Od3I.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
6
|
+
//#region src/primitives/button.tsx
|
|
7
|
+
const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 disabled:saturate-0 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:saturate-0 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", {
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
11
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
12
|
+
outline: "border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground",
|
|
13
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
14
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
15
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
16
|
+
},
|
|
17
|
+
size: {
|
|
18
|
+
default: "h-9 px-4 py-2",
|
|
19
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
20
|
+
lg: "h-10 rounded-md px-6",
|
|
21
|
+
icon: "h-9 w-9"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "default",
|
|
26
|
+
size: "default"
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
30
|
+
return jsx(asChild ? Slot : "button", {
|
|
31
|
+
className: cn(buttonVariants({
|
|
32
|
+
variant,
|
|
33
|
+
size,
|
|
34
|
+
className
|
|
35
|
+
})),
|
|
36
|
+
ref,
|
|
37
|
+
...props
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
Button.displayName = "Button";
|
|
41
|
+
//#endregion
|
|
42
|
+
export { buttonVariants as n, Button as t };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=button-MWVeZaqj.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button-MWVeZaqj.js","names":[],"sources":["../src/primitives/button.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn.js';\n\nconst buttonVariants = cva(\n 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 disabled:saturate-0 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:saturate-0 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n outline:\n 'border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground',\n secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n // Shadcn new-york current defaults. The shared stylesheet keeps the\n // standard 16px root; editable controls choose responsive typography\n // locally when they need mobile `text-base` and desktop `md:text-sm`.\n default: 'h-9 px-4 py-2',\n sm: 'h-8 rounded-md px-3 text-xs',\n lg: 'h-10 rounded-md px-6',\n icon: 'h-9 w-9',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n return (\n <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />\n );\n },\n);\nButton.displayName = 'Button';\n\nexport { Button, buttonVariants };\n"],"mappings":";;;;;;AAOA,MAAM,iBAAiB,IACrB,ieACA;CACE,UAAU;EACR,SAAS;GACP,SAAS;GACT,aAAa;GACb,SACE;GACF,WAAW;GACX,OAAO;GACP,MAAM;EACR;EACA,MAAM;GAIJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,MAAM;EACR;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;AAQA,MAAM,SAAS,MAAM,YAClB,EAAE,WAAW,SAAS,MAAM,UAAU,OAAO,GAAG,SAAS,QAAQ;CAEhE,OACE,IAFW,UAAU,OAAO,UAE5B;EAAM,WAAW,GAAG,eAAe;GAAE;GAAS;GAAM;EAAU,CAAC,CAAC;EAAQ;EAAK,GAAI;CAAQ,CAAA;AAE7F,CACF;AACA,OAAO,cAAc"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { resolveCandidates, resolveFieldLabel } from "@burojs/core";
|
|
2
|
+
import { useApp, useCandidatesAccess, useI18nCaps, useOptionalT, useTestIds } from "@burojs/react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
//#region src/fields/candidates-gate.ts
|
|
5
|
+
const NO_SOURCES = Object.freeze({});
|
|
6
|
+
const NO_FILTERS = Object.freeze([]);
|
|
7
|
+
function useCandidatesGate(input) {
|
|
8
|
+
const { to, candidates, sources, resource, fieldName } = input;
|
|
9
|
+
const t = useOptionalT();
|
|
10
|
+
const app = useApp();
|
|
11
|
+
const tid = useTestIds();
|
|
12
|
+
const ownerCaps = useI18nCaps(resource);
|
|
13
|
+
const targetCaps = useI18nCaps(to ?? resource);
|
|
14
|
+
const access = useCandidatesAccess(to ?? "", candidates);
|
|
15
|
+
const resolution = React.useMemo(() => resolveCandidates(candidates, sources ?? NO_SOURCES), [candidates, sources]);
|
|
16
|
+
if (candidates === void 0 || candidates.length === 0) return { filters: NO_FILTERS };
|
|
17
|
+
const testId = tid.relationCandidatesHint(resource, fieldName);
|
|
18
|
+
if (access.status === "pending") return {
|
|
19
|
+
filters: NO_FILTERS,
|
|
20
|
+
gate: { kind: "pending" }
|
|
21
|
+
};
|
|
22
|
+
if (access.status === "forbidden") {
|
|
23
|
+
const declared = (app.registry.get(to ?? "")?.fields)?.[access.field]?.label;
|
|
24
|
+
const label = resolveFieldLabel(targetCaps, access.field, typeof declared === "string" ? declared : void 0);
|
|
25
|
+
return {
|
|
26
|
+
filters: NO_FILTERS,
|
|
27
|
+
gate: {
|
|
28
|
+
kind: "forbidden",
|
|
29
|
+
testId,
|
|
30
|
+
message: t("relationPicker.cannotSearchBy", {
|
|
31
|
+
params: { label },
|
|
32
|
+
defaultValue: `You cannot search by ${label}`
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (resolution.status === "pending") return {
|
|
38
|
+
filters: NO_FILTERS,
|
|
39
|
+
gate: { kind: "pending" }
|
|
40
|
+
};
|
|
41
|
+
if (resolution.status === "empty") {
|
|
42
|
+
const { source, path } = resolution.hint;
|
|
43
|
+
const head = path[0];
|
|
44
|
+
const declared = (source === "values" || source === "record") && head !== void 0 ? (app.registry.get(resource)?.fields)?.[head]?.label : void 0;
|
|
45
|
+
const label = resolveFieldLabel(ownerCaps, head ?? source, typeof declared === "string" ? declared : void 0);
|
|
46
|
+
return {
|
|
47
|
+
filters: NO_FILTERS,
|
|
48
|
+
gate: {
|
|
49
|
+
kind: "empty",
|
|
50
|
+
testId,
|
|
51
|
+
message: t("relationPicker.chooseFirst", {
|
|
52
|
+
params: { label },
|
|
53
|
+
defaultValue: `Choose ${label} first`
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { filters: resolution.filters };
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { useCandidatesGate as t };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=candidates-gate-CX2PBu6l.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"candidates-gate-CX2PBu6l.js","names":[],"sources":["../src/fields/candidates-gate.ts"],"sourcesContent":["import {\n type FixedFilterInput,\n type RelationCandidate,\n type ScreenSources,\n resolveCandidates,\n resolveFieldLabel,\n} from '@burojs/core';\nimport { useApp, useCandidatesAccess, useI18nCaps, useOptionalT, useTestIds } from '@burojs/react';\nimport * as React from 'react';\n\n/** A select that must not read its target now (P7 3.4): why, and what it tells the user. */\nexport type CandidatesGate =\n | { readonly kind: 'pending' }\n | { readonly kind: 'empty'; readonly message: string; readonly testId: string }\n | { readonly kind: 'forbidden'; readonly message: string; readonly testId: string };\n\nconst NO_SOURCES: ScreenSources = Object.freeze({});\nconst NO_FILTERS: readonly FixedFilterInput[] = Object.freeze([]);\n\n/**\n * A relation field's `candidates` as the select applies them (P7 3.4): the filters to send and,\n * when they say the target must not be read yet, the gate that tells the user why. The refs are\n * read from the owner's projected sources, so a field P5 hides reads `null` (the select asks for\n * it first, and sends nothing); a candidate on a target field the user may not view forbids the\n * read; an undecided source or permission map keeps the select disabled.\n */\nexport function useCandidatesGate(input: {\n readonly to: string | undefined;\n readonly candidates: readonly RelationCandidate[] | undefined;\n readonly sources: ScreenSources | undefined;\n readonly resource: string;\n readonly fieldName: string;\n}): { readonly filters: readonly FixedFilterInput[]; readonly gate?: CandidatesGate } {\n const { to, candidates, sources, resource, fieldName } = input;\n const t = useOptionalT();\n const app = useApp();\n const tid = useTestIds();\n const ownerCaps = useI18nCaps(resource);\n const targetCaps = useI18nCaps(to ?? resource);\n const access = useCandidatesAccess(to ?? '', candidates);\n const resolution = React.useMemo(\n () => resolveCandidates(candidates, sources ?? NO_SOURCES),\n [candidates, sources],\n );\n if (candidates === undefined || candidates.length === 0) return { filters: NO_FILTERS };\n const testId = tid.relationCandidatesHint(resource, fieldName);\n if (access.status === 'pending') return { filters: NO_FILTERS, gate: { kind: 'pending' } };\n if (access.status === 'forbidden') {\n const declared = (\n app.registry.get(to ?? '')?.fields as Record<string, { label?: unknown }> | undefined\n )?.[access.field]?.label;\n const label = resolveFieldLabel(\n targetCaps,\n access.field,\n typeof declared === 'string' ? declared : undefined,\n );\n return {\n filters: NO_FILTERS,\n gate: {\n kind: 'forbidden',\n testId,\n message: t('relationPicker.cannotSearchBy', {\n params: { label },\n defaultValue: `You cannot search by ${label}`,\n }),\n },\n };\n }\n if (resolution.status === 'pending') return { filters: NO_FILTERS, gate: { kind: 'pending' } };\n if (resolution.status === 'empty') {\n const { source, path } = resolution.hint;\n const head = path[0];\n // The field a `values` / `record` ref reads is labelled as the owner labels it; any other\n // source (`scope`, `user`) names the key it reads, humanised.\n const own = source === 'values' || source === 'record';\n const declared =\n own && head !== undefined\n ? (app.registry.get(resource)?.fields as Record<string, { label?: unknown }> | undefined)?.[\n head\n ]?.label\n : undefined;\n const label = resolveFieldLabel(\n ownerCaps,\n head ?? source,\n typeof declared === 'string' ? declared : undefined,\n );\n return {\n filters: NO_FILTERS,\n gate: {\n kind: 'empty',\n testId,\n message: t('relationPicker.chooseFirst', {\n params: { label },\n defaultValue: `Choose ${label} first`,\n }),\n },\n };\n }\n return { filters: resolution.filters };\n}\n"],"mappings":";;;;AAgBA,MAAM,aAA4B,OAAO,OAAO,CAAC,CAAC;AAClD,MAAM,aAA0C,OAAO,OAAO,CAAC,CAAC;AAShE,SAAgB,kBAAkB,OAMoD;CACpF,MAAM,EAAE,IAAI,YAAY,SAAS,UAAU,cAAc;CACzD,MAAM,IAAI,aAAa;CACvB,MAAM,MAAM,OAAO;CACnB,MAAM,MAAM,WAAW;CACvB,MAAM,YAAY,YAAY,QAAQ;CACtC,MAAM,aAAa,YAAY,MAAM,QAAQ;CAC7C,MAAM,SAAS,oBAAoB,MAAM,IAAI,UAAU;CACvD,MAAM,aAAa,MAAM,cACjB,kBAAkB,YAAY,WAAW,UAAU,GACzD,CAAC,YAAY,OAAO,CACtB;CACA,IAAI,eAAe,KAAA,KAAa,WAAW,WAAW,GAAG,OAAO,EAAE,SAAS,WAAW;CACtF,MAAM,SAAS,IAAI,uBAAuB,UAAU,SAAS;CAC7D,IAAI,OAAO,WAAW,WAAW,OAAO;EAAE,SAAS;EAAY,MAAM,EAAE,MAAM,UAAU;CAAE;CACzF,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,YACJ,IAAI,SAAS,IAAI,MAAM,EAAE,CAAC,EAAE,OAAA,GAC1B,OAAO,MAAM,EAAE;EACnB,MAAM,QAAQ,kBACZ,YACA,OAAO,OACP,OAAO,aAAa,WAAW,WAAW,KAAA,CAC5C;EACA,OAAO;GACL,SAAS;GACT,MAAM;IACJ,MAAM;IACN;IACA,SAAS,EAAE,iCAAiC;KAC1C,QAAQ,EAAE,MAAM;KAChB,cAAc,wBAAwB;IACxC,CAAC;GACH;EACF;CACF;CACA,IAAI,WAAW,WAAW,WAAW,OAAO;EAAE,SAAS;EAAY,MAAM,EAAE,MAAM,UAAU;CAAE;CAC7F,IAAI,WAAW,WAAW,SAAS;EACjC,MAAM,EAAE,QAAQ,SAAS,WAAW;EACpC,MAAM,OAAO,KAAK;EAIlB,MAAM,YADM,WAAW,YAAY,WAAW,aAErC,SAAS,KAAA,KACX,IAAI,SAAS,IAAI,QAAQ,CAAC,EAAE,OAAA,GAC3B,KACD,EAAE,QACH,KAAA;EACN,MAAM,QAAQ,kBACZ,WACA,QAAQ,QACR,OAAO,aAAa,WAAW,WAAW,KAAA,CAC5C;EACA,OAAO;GACL,SAAS;GACT,MAAM;IACJ,MAAM;IACN;IACA,SAAS,EAAE,8BAA8B;KACvC,QAAQ,EAAE,MAAM;KAChB,cAAc,UAAU,MAAM;IAChC,CAAC;GACH;EACF;CACF;CACA,OAAO,EAAE,SAAS,WAAW,QAAQ;AACvC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const require_use_theme = require("./use-theme-DFlpXils.cjs");
|
|
2
|
+
let _burojs_core = require("@burojs/core");
|
|
3
|
+
let _burojs_react = require("@burojs/react");
|
|
4
|
+
let react = require("react");
|
|
5
|
+
react = require_use_theme.__toESM(react, 1);
|
|
6
|
+
//#region src/fields/candidates-gate.ts
|
|
7
|
+
const NO_SOURCES = Object.freeze({});
|
|
8
|
+
const NO_FILTERS = Object.freeze([]);
|
|
9
|
+
function useCandidatesGate(input) {
|
|
10
|
+
const { to, candidates, sources, resource, fieldName } = input;
|
|
11
|
+
const t = (0, _burojs_react.useOptionalT)();
|
|
12
|
+
const app = (0, _burojs_react.useApp)();
|
|
13
|
+
const tid = (0, _burojs_react.useTestIds)();
|
|
14
|
+
const ownerCaps = (0, _burojs_react.useI18nCaps)(resource);
|
|
15
|
+
const targetCaps = (0, _burojs_react.useI18nCaps)(to ?? resource);
|
|
16
|
+
const access = (0, _burojs_react.useCandidatesAccess)(to ?? "", candidates);
|
|
17
|
+
const resolution = react.useMemo(() => (0, _burojs_core.resolveCandidates)(candidates, sources ?? NO_SOURCES), [candidates, sources]);
|
|
18
|
+
if (candidates === void 0 || candidates.length === 0) return { filters: NO_FILTERS };
|
|
19
|
+
const testId = tid.relationCandidatesHint(resource, fieldName);
|
|
20
|
+
if (access.status === "pending") return {
|
|
21
|
+
filters: NO_FILTERS,
|
|
22
|
+
gate: { kind: "pending" }
|
|
23
|
+
};
|
|
24
|
+
if (access.status === "forbidden") {
|
|
25
|
+
const declared = (app.registry.get(to ?? "")?.fields)?.[access.field]?.label;
|
|
26
|
+
const label = (0, _burojs_core.resolveFieldLabel)(targetCaps, access.field, typeof declared === "string" ? declared : void 0);
|
|
27
|
+
return {
|
|
28
|
+
filters: NO_FILTERS,
|
|
29
|
+
gate: {
|
|
30
|
+
kind: "forbidden",
|
|
31
|
+
testId,
|
|
32
|
+
message: t("relationPicker.cannotSearchBy", {
|
|
33
|
+
params: { label },
|
|
34
|
+
defaultValue: `You cannot search by ${label}`
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (resolution.status === "pending") return {
|
|
40
|
+
filters: NO_FILTERS,
|
|
41
|
+
gate: { kind: "pending" }
|
|
42
|
+
};
|
|
43
|
+
if (resolution.status === "empty") {
|
|
44
|
+
const { source, path } = resolution.hint;
|
|
45
|
+
const head = path[0];
|
|
46
|
+
const declared = (source === "values" || source === "record") && head !== void 0 ? (app.registry.get(resource)?.fields)?.[head]?.label : void 0;
|
|
47
|
+
const label = (0, _burojs_core.resolveFieldLabel)(ownerCaps, head ?? source, typeof declared === "string" ? declared : void 0);
|
|
48
|
+
return {
|
|
49
|
+
filters: NO_FILTERS,
|
|
50
|
+
gate: {
|
|
51
|
+
kind: "empty",
|
|
52
|
+
testId,
|
|
53
|
+
message: t("relationPicker.chooseFirst", {
|
|
54
|
+
params: { label },
|
|
55
|
+
defaultValue: `Choose ${label} first`
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { filters: resolution.filters };
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
Object.defineProperty(exports, "useCandidatesGate", {
|
|
64
|
+
enumerable: true,
|
|
65
|
+
get: function() {
|
|
66
|
+
return useCandidatesGate;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=candidates-gate-DiFI52r7.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"candidates-gate-DiFI52r7.cjs","names":["useOptionalT","useApp","useTestIds","useI18nCaps","useCandidatesAccess","React","resolveCandidates","resolveFieldLabel"],"sources":["../src/fields/candidates-gate.ts"],"sourcesContent":["import {\n type FixedFilterInput,\n type RelationCandidate,\n type ScreenSources,\n resolveCandidates,\n resolveFieldLabel,\n} from '@burojs/core';\nimport { useApp, useCandidatesAccess, useI18nCaps, useOptionalT, useTestIds } from '@burojs/react';\nimport * as React from 'react';\n\n/** A select that must not read its target now (P7 3.4): why, and what it tells the user. */\nexport type CandidatesGate =\n | { readonly kind: 'pending' }\n | { readonly kind: 'empty'; readonly message: string; readonly testId: string }\n | { readonly kind: 'forbidden'; readonly message: string; readonly testId: string };\n\nconst NO_SOURCES: ScreenSources = Object.freeze({});\nconst NO_FILTERS: readonly FixedFilterInput[] = Object.freeze([]);\n\n/**\n * A relation field's `candidates` as the select applies them (P7 3.4): the filters to send and,\n * when they say the target must not be read yet, the gate that tells the user why. The refs are\n * read from the owner's projected sources, so a field P5 hides reads `null` (the select asks for\n * it first, and sends nothing); a candidate on a target field the user may not view forbids the\n * read; an undecided source or permission map keeps the select disabled.\n */\nexport function useCandidatesGate(input: {\n readonly to: string | undefined;\n readonly candidates: readonly RelationCandidate[] | undefined;\n readonly sources: ScreenSources | undefined;\n readonly resource: string;\n readonly fieldName: string;\n}): { readonly filters: readonly FixedFilterInput[]; readonly gate?: CandidatesGate } {\n const { to, candidates, sources, resource, fieldName } = input;\n const t = useOptionalT();\n const app = useApp();\n const tid = useTestIds();\n const ownerCaps = useI18nCaps(resource);\n const targetCaps = useI18nCaps(to ?? resource);\n const access = useCandidatesAccess(to ?? '', candidates);\n const resolution = React.useMemo(\n () => resolveCandidates(candidates, sources ?? NO_SOURCES),\n [candidates, sources],\n );\n if (candidates === undefined || candidates.length === 0) return { filters: NO_FILTERS };\n const testId = tid.relationCandidatesHint(resource, fieldName);\n if (access.status === 'pending') return { filters: NO_FILTERS, gate: { kind: 'pending' } };\n if (access.status === 'forbidden') {\n const declared = (\n app.registry.get(to ?? '')?.fields as Record<string, { label?: unknown }> | undefined\n )?.[access.field]?.label;\n const label = resolveFieldLabel(\n targetCaps,\n access.field,\n typeof declared === 'string' ? declared : undefined,\n );\n return {\n filters: NO_FILTERS,\n gate: {\n kind: 'forbidden',\n testId,\n message: t('relationPicker.cannotSearchBy', {\n params: { label },\n defaultValue: `You cannot search by ${label}`,\n }),\n },\n };\n }\n if (resolution.status === 'pending') return { filters: NO_FILTERS, gate: { kind: 'pending' } };\n if (resolution.status === 'empty') {\n const { source, path } = resolution.hint;\n const head = path[0];\n // The field a `values` / `record` ref reads is labelled as the owner labels it; any other\n // source (`scope`, `user`) names the key it reads, humanised.\n const own = source === 'values' || source === 'record';\n const declared =\n own && head !== undefined\n ? (app.registry.get(resource)?.fields as Record<string, { label?: unknown }> | undefined)?.[\n head\n ]?.label\n : undefined;\n const label = resolveFieldLabel(\n ownerCaps,\n head ?? source,\n typeof declared === 'string' ? declared : undefined,\n );\n return {\n filters: NO_FILTERS,\n gate: {\n kind: 'empty',\n testId,\n message: t('relationPicker.chooseFirst', {\n params: { label },\n defaultValue: `Choose ${label} first`,\n }),\n },\n };\n }\n return { filters: resolution.filters };\n}\n"],"mappings":";;;;;;AAgBA,MAAM,aAA4B,OAAO,OAAO,CAAC,CAAC;AAClD,MAAM,aAA0C,OAAO,OAAO,CAAC,CAAC;AAShE,SAAgB,kBAAkB,OAMoD;CACpF,MAAM,EAAE,IAAI,YAAY,SAAS,UAAU,cAAc;CACzD,MAAM,KAAA,GAAIA,cAAAA,aAAAA,CAAa;CACvB,MAAM,OAAA,GAAMC,cAAAA,OAAAA,CAAO;CACnB,MAAM,OAAA,GAAMC,cAAAA,WAAAA,CAAW;CACvB,MAAM,aAAA,GAAYC,cAAAA,YAAAA,CAAY,QAAQ;CACtC,MAAM,cAAA,GAAaA,cAAAA,YAAAA,CAAY,MAAM,QAAQ;CAC7C,MAAM,UAAA,GAASC,cAAAA,oBAAAA,CAAoB,MAAM,IAAI,UAAU;CACvD,MAAM,aAAaC,MAAM,eAAA,GACjBC,aAAAA,kBAAAA,CAAkB,YAAY,WAAW,UAAU,GACzD,CAAC,YAAY,OAAO,CACtB;CACA,IAAI,eAAe,KAAA,KAAa,WAAW,WAAW,GAAG,OAAO,EAAE,SAAS,WAAW;CACtF,MAAM,SAAS,IAAI,uBAAuB,UAAU,SAAS;CAC7D,IAAI,OAAO,WAAW,WAAW,OAAO;EAAE,SAAS;EAAY,MAAM,EAAE,MAAM,UAAU;CAAE;CACzF,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,YACJ,IAAI,SAAS,IAAI,MAAM,EAAE,CAAC,EAAE,OAAA,GAC1B,OAAO,MAAM,EAAE;EACnB,MAAM,SAAA,GAAQC,aAAAA,kBAAAA,CACZ,YACA,OAAO,OACP,OAAO,aAAa,WAAW,WAAW,KAAA,CAC5C;EACA,OAAO;GACL,SAAS;GACT,MAAM;IACJ,MAAM;IACN;IACA,SAAS,EAAE,iCAAiC;KAC1C,QAAQ,EAAE,MAAM;KAChB,cAAc,wBAAwB;IACxC,CAAC;GACH;EACF;CACF;CACA,IAAI,WAAW,WAAW,WAAW,OAAO;EAAE,SAAS;EAAY,MAAM,EAAE,MAAM,UAAU;CAAE;CAC7F,IAAI,WAAW,WAAW,SAAS;EACjC,MAAM,EAAE,QAAQ,SAAS,WAAW;EACpC,MAAM,OAAO,KAAK;EAIlB,MAAM,YADM,WAAW,YAAY,WAAW,aAErC,SAAS,KAAA,KACX,IAAI,SAAS,IAAI,QAAQ,CAAC,EAAE,OAAA,GAC3B,KACD,EAAE,QACH,KAAA;EACN,MAAM,SAAA,GAAQA,aAAAA,kBAAAA,CACZ,WACA,QAAQ,QACR,OAAO,aAAa,WAAW,WAAW,KAAA,CAC5C;EACA,OAAO;GACL,SAAS;GACT,MAAM;IACJ,MAAM;IACN;IACA,SAAS,EAAE,8BAA8B;KACvC,QAAQ,EAAE,MAAM;KAChB,cAAc,UAAU,MAAM;IAChC,CAAC;GACH;EACF;CACF;CACA,OAAO,EAAE,SAAS,WAAW,QAAQ;AACvC"}
|