@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
|
@@ -0,0 +1,1779 @@
|
|
|
1
|
+
const require_check_confirm_dialog = require("./check-confirm-dialog-BRByTFpg.cjs");
|
|
2
|
+
const require_button = require("./button-BxnnE28H.cjs");
|
|
3
|
+
const require_skeleton = require("./skeleton-_1gP7wan.cjs");
|
|
4
|
+
const require_place_node = require("./place-node-SIp4XYQH.cjs");
|
|
5
|
+
let _burojs_core = require("@burojs/core");
|
|
6
|
+
let _burojs_react = require("@burojs/react");
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
//#region src/resource-key.ts
|
|
10
|
+
function canonicalNavKey(node) {
|
|
11
|
+
return (0, _burojs_core.canonicalEntityNodeKey)(node);
|
|
12
|
+
}
|
|
13
|
+
function canonicalResourceKey(def) {
|
|
14
|
+
return canonicalNavKey(def);
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/layout/build-nav-items.ts
|
|
18
|
+
function buildNavItems(args) {
|
|
19
|
+
const { registry, caps, groupName, match, isVisible, entryFilter, icons } = args;
|
|
20
|
+
const chain = match?.groupChain ?? [];
|
|
21
|
+
const qualifiedMatch = match;
|
|
22
|
+
const activeLeaf = qualifiedMatch == null ? void 0 : qualifiedMatch.kind === "group-index" ? canonicalNavKey({
|
|
23
|
+
name: qualifiedMatch.target ?? qualifiedMatch.name,
|
|
24
|
+
ref: qualifiedMatch.targetRef
|
|
25
|
+
}) : canonicalNavKey(qualifiedMatch);
|
|
26
|
+
const isEligible = (node) => isVisible(node) && (entryFilter ? entryFilter(node) : true);
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const child of registry.groups.childrenOf(groupName)) {
|
|
29
|
+
if (!isEligible(child)) continue;
|
|
30
|
+
const kind = (0, _burojs_core.nodeKind)(child);
|
|
31
|
+
const childKey = canonicalNavKey(child);
|
|
32
|
+
const href = (0, _burojs_core.navHrefFor)(registry, childKey, { isVisible: isEligible });
|
|
33
|
+
if (kind === "group") {
|
|
34
|
+
const label = (0, _burojs_core.resolveNavLabel)(caps, `groups.${childKey}`, child.label ?? child.name);
|
|
35
|
+
const active = chain.some((g) => canonicalNavKey(g) === childKey);
|
|
36
|
+
out.push({
|
|
37
|
+
kind: "group",
|
|
38
|
+
name: childKey,
|
|
39
|
+
label,
|
|
40
|
+
href,
|
|
41
|
+
icon: icons?.group?.(childKey),
|
|
42
|
+
active
|
|
43
|
+
});
|
|
44
|
+
} else if (kind === "custom-page") out.push({
|
|
45
|
+
kind: "custom-page",
|
|
46
|
+
name: childKey,
|
|
47
|
+
label: (0, _burojs_core.resolveNavLabel)(caps, `pages.${childKey}`, child.label ?? child.name),
|
|
48
|
+
href,
|
|
49
|
+
icon: icons?.page?.(childKey),
|
|
50
|
+
active: activeLeaf === childKey
|
|
51
|
+
});
|
|
52
|
+
else out.push({
|
|
53
|
+
kind: "resource",
|
|
54
|
+
name: childKey,
|
|
55
|
+
label: (0, _burojs_core.resolveResourceNavLabel)(caps, childKey, child),
|
|
56
|
+
href,
|
|
57
|
+
icon: icons?.resource?.(childKey),
|
|
58
|
+
active: activeLeaf === childKey
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/layout/nav-slots.ts
|
|
65
|
+
function layoutSlot(key) {
|
|
66
|
+
return `layout.${key}`;
|
|
67
|
+
}
|
|
68
|
+
function useLayout(key) {
|
|
69
|
+
return (0, _burojs_react.useComponent)(layoutSlot(key));
|
|
70
|
+
}
|
|
71
|
+
function useLayoutResolver() {
|
|
72
|
+
const components = (0, react.useContext)(_burojs_react.ComponentsContext);
|
|
73
|
+
return (key) => components[layoutSlot(key)];
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/layout/use-nav-routing.ts
|
|
77
|
+
function useNavVisible() {
|
|
78
|
+
const ctx = (0, _burojs_react.useVisibilityCtx)();
|
|
79
|
+
const allowed = usePageAllowed();
|
|
80
|
+
return (0, react.useCallback)((node) => (0, _burojs_core.evaluateVisible)(node.visible, ctx) && (!(0, _burojs_core.isCustomPageDef)(node) || allowed(node)), [ctx, allowed]);
|
|
81
|
+
}
|
|
82
|
+
function usePageAllowed() {
|
|
83
|
+
const permissions = (0, _burojs_react.usePermissions)();
|
|
84
|
+
return (0, react.useCallback)((page) => (0, _burojs_react.pageAccessFor)(page, permissions) === "allowed", [permissions]);
|
|
85
|
+
}
|
|
86
|
+
function useHref(name, opts) {
|
|
87
|
+
const app = (0, _burojs_react.useApp)();
|
|
88
|
+
const isVisible = useNavVisible();
|
|
89
|
+
return (0, _burojs_core.hrefFor)(app.registry, name, {
|
|
90
|
+
...opts,
|
|
91
|
+
isVisible
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function useMatch() {
|
|
95
|
+
const app = (0, _burojs_react.useApp)();
|
|
96
|
+
const isVisible = useNavVisible();
|
|
97
|
+
const path = (0, _burojs_react.useOptionalRouterAdapter)()?.pathname;
|
|
98
|
+
return path === void 0 ? null : (0, _burojs_core.matchPath)(app.registry, path, { isVisible });
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/layout/nav-chrome-boundary.tsx
|
|
102
|
+
const NavChromeBoundaryContext = (0, react.createContext)(new Set());
|
|
103
|
+
function useNavChromeBoundary() {
|
|
104
|
+
return (0, react.useContext)(NavChromeBoundaryContext);
|
|
105
|
+
}
|
|
106
|
+
function NavChromeBoundaryProvider({ claim, children }) {
|
|
107
|
+
const parent = (0, react.useContext)(NavChromeBoundaryContext);
|
|
108
|
+
const key = claim.join(" ");
|
|
109
|
+
const merged = (0, react.useMemo)(() => {
|
|
110
|
+
const s = new Set(parent);
|
|
111
|
+
for (const n of claim) s.add(n);
|
|
112
|
+
return s;
|
|
113
|
+
}, [parent, key]);
|
|
114
|
+
return (0, react_jsx_runtime.jsx)(NavChromeBoundaryContext.Provider, {
|
|
115
|
+
value: merged,
|
|
116
|
+
children
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/layout/nav-chrome.tsx
|
|
121
|
+
const DETAIL_ACTIONS = new Set([
|
|
122
|
+
"show",
|
|
123
|
+
"edit",
|
|
124
|
+
"create"
|
|
125
|
+
]);
|
|
126
|
+
function NavChrome({ children, entryFilter, icons }) {
|
|
127
|
+
const app = (0, _burojs_react.useApp)();
|
|
128
|
+
const caps = (0, _burojs_react.useI18nCaps)();
|
|
129
|
+
const isVisible = useNavVisible();
|
|
130
|
+
const match = useMatch();
|
|
131
|
+
const resolveLayout = useLayoutResolver();
|
|
132
|
+
const boundary = useNavChromeBoundary();
|
|
133
|
+
if (!match) return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children });
|
|
134
|
+
let content = children;
|
|
135
|
+
for (let i = match.groupChain.length - 1; i >= 0; i--) {
|
|
136
|
+
const group = match.groupChain[i];
|
|
137
|
+
if (!group) continue;
|
|
138
|
+
const groupKey = canonicalNavKey(group);
|
|
139
|
+
if (boundary.has(groupKey)) continue;
|
|
140
|
+
if (group.chromeOn === "list" && match.kind === "resource" && DETAIL_ACTIONS.has(match.action)) continue;
|
|
141
|
+
const Layout = resolveLayout(group.layout);
|
|
142
|
+
if (!Layout) continue;
|
|
143
|
+
const items = buildNavItems({
|
|
144
|
+
registry: app.registry,
|
|
145
|
+
caps,
|
|
146
|
+
groupName: groupKey,
|
|
147
|
+
match,
|
|
148
|
+
isVisible,
|
|
149
|
+
entryFilter,
|
|
150
|
+
icons
|
|
151
|
+
});
|
|
152
|
+
content = (0, react_jsx_runtime.jsx)(Layout, {
|
|
153
|
+
group,
|
|
154
|
+
groupKey,
|
|
155
|
+
items,
|
|
156
|
+
children: content
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: content });
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/record/record-state.tsx
|
|
163
|
+
function RecordState({ state, onRetry }) {
|
|
164
|
+
const t = (0, _burojs_react.useT)();
|
|
165
|
+
if (state === "loading") return (0, react_jsx_runtime.jsx)("div", {
|
|
166
|
+
role: "status",
|
|
167
|
+
"aria-busy": "true",
|
|
168
|
+
"data-testid": "record-page:loading",
|
|
169
|
+
className: "p-6 text-sm text-muted-foreground",
|
|
170
|
+
children: t("router.recordLoading")
|
|
171
|
+
});
|
|
172
|
+
const message = state === "not-found" ? t("router.recordNotFound") : state === "session-expired" ? t("router.recordSessionExpired") : t("router.recordError");
|
|
173
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
174
|
+
role: "alert",
|
|
175
|
+
"data-testid": `record-page:${state}`,
|
|
176
|
+
className: "flex flex-col items-center justify-center gap-3 px-6 py-24 text-center",
|
|
177
|
+
children: [(0, react_jsx_runtime.jsx)("p", {
|
|
178
|
+
className: "max-w-sm text-sm text-muted-foreground",
|
|
179
|
+
children: message
|
|
180
|
+
}), onRetry ? (0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
181
|
+
type: "button",
|
|
182
|
+
variant: "outline",
|
|
183
|
+
size: "sm",
|
|
184
|
+
"data-testid": "record-page:retry",
|
|
185
|
+
onClick: onRetry,
|
|
186
|
+
children: t("actions.retry")
|
|
187
|
+
}) : null]
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/flows/page-flow-loader.tsx
|
|
192
|
+
function PageFlowRouteLoader({ match }) {
|
|
193
|
+
const t = (0, _burojs_react.useOptionalT)();
|
|
194
|
+
const loaded = require_check_confirm_dialog.useLoaded(require_check_confirm_dialog.loadPageFlowRoute);
|
|
195
|
+
const failed = (retry) => (0, react_jsx_runtime.jsxs)("div", {
|
|
196
|
+
role: "alert",
|
|
197
|
+
"data-buro-flow-load": "failed",
|
|
198
|
+
className: "flex flex-col gap-3 p-6 text-sm",
|
|
199
|
+
children: [(0, react_jsx_runtime.jsx)("p", { children: require_check_confirm_dialog.loadFailedText(t) }), (0, react_jsx_runtime.jsx)("div", { children: (0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
200
|
+
type: "button",
|
|
201
|
+
onClick: retry,
|
|
202
|
+
children: t("actions.retry") === "actions.retry" ? "Retry" : t("actions.retry")
|
|
203
|
+
}) })]
|
|
204
|
+
});
|
|
205
|
+
if (loaded.error !== void 0) return failed(loaded.retry);
|
|
206
|
+
if (loaded.module === void 0) return (0, react_jsx_runtime.jsx)(RecordState, { state: "loading" });
|
|
207
|
+
const Route = loaded.module.PageFlowRoute;
|
|
208
|
+
return (0, react_jsx_runtime.jsx)(require_check_confirm_dialog.FlowBoundary, {
|
|
209
|
+
fallback: (_error, reset) => failed(reset),
|
|
210
|
+
children: (0, react_jsx_runtime.jsx)(Route, { match })
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/list-kit/resource-list-page.tsx
|
|
215
|
+
function ResourceListPage({ resource, meta, renderCell, emptyState }) {
|
|
216
|
+
const app = (0, _burojs_react.useRouteListImplementation)();
|
|
217
|
+
const implementation = require_place_node.selectImplementation("buro:related-list", app);
|
|
218
|
+
const View = implementation === void 0 ? void 0 : require_place_node.viewOf(implementation);
|
|
219
|
+
const translate = (0, _burojs_react.useTranslate)();
|
|
220
|
+
const route = (0, react.useMemo)(() => ({
|
|
221
|
+
meta,
|
|
222
|
+
renderCell,
|
|
223
|
+
emptyState
|
|
224
|
+
}), [
|
|
225
|
+
meta,
|
|
226
|
+
renderCell,
|
|
227
|
+
emptyState
|
|
228
|
+
]);
|
|
229
|
+
const viewProps = (0, react.useMemo)(() => ({
|
|
230
|
+
props: { resource: (0, _burojs_core.normalizeEntityRef)(resource) },
|
|
231
|
+
data: {},
|
|
232
|
+
t: (key, options) => translate(`blocks.buro:related-list.${key}`, options)
|
|
233
|
+
}), [resource, translate]);
|
|
234
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
235
|
+
"data-buro-node": "place",
|
|
236
|
+
"data-buro-size": "full",
|
|
237
|
+
className: "min-w-0",
|
|
238
|
+
children: (0, react_jsx_runtime.jsx)(require_place_node.RouteListContext.Provider, {
|
|
239
|
+
value: route,
|
|
240
|
+
children: (0, react_jsx_runtime.jsx)(react.Suspense, {
|
|
241
|
+
fallback: null,
|
|
242
|
+
children: View === void 0 ? null : (0, react_jsx_runtime.jsx)(View, { ...viewProps })
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
//#endregion
|
|
248
|
+
//#region src/layout/page-host.tsx
|
|
249
|
+
function PageHost({ page, match, notFound }) {
|
|
250
|
+
const isVisible = useNavVisible();
|
|
251
|
+
const ctx = (0, _burojs_react.useVisibilityCtx)();
|
|
252
|
+
const access = (0, _burojs_react.usePageAccess)(page);
|
|
253
|
+
if (!((0, _burojs_core.evaluateVisible)(page.visible, ctx) && match.groupChain.every((group) => isVisible(group)))) return notFound;
|
|
254
|
+
if (access === "pending") return (0, react_jsx_runtime.jsx)(RecordState, { state: "loading" });
|
|
255
|
+
if (access === "denied") return notFound;
|
|
256
|
+
return (0, react_jsx_runtime.jsx)(PageParams, {
|
|
257
|
+
page,
|
|
258
|
+
notFound
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const NO_SEARCH = new URLSearchParams();
|
|
262
|
+
function PageParams({ page, notFound }) {
|
|
263
|
+
const search = (0, _burojs_react.useOptionalRouterAdapter)()?.searchParams ?? (typeof window === "undefined" ? NO_SEARCH : new URLSearchParams(window.location.search));
|
|
264
|
+
const raw = JSON.stringify((0, _burojs_core.pageParamNames)(page.params).map((name) => search.get(name)));
|
|
265
|
+
const parsed = (0, react.useMemo)(() => (0, _burojs_core.parsePageParams)(page, search), [page, raw]);
|
|
266
|
+
if (!parsed.ok) return notFound;
|
|
267
|
+
return (0, react_jsx_runtime.jsx)(_burojs_react.RouteSourceContext.Provider, {
|
|
268
|
+
value: parsed.route,
|
|
269
|
+
children: (0, react_jsx_runtime.jsx)(PageBody, { page })
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function PageBody({ page }) {
|
|
273
|
+
const snapshot = (0, _burojs_react.useApplicationSnapshot)();
|
|
274
|
+
const compiled = (0, react.useMemo)(() => (0, _burojs_core.compilePageScreen)(page, {
|
|
275
|
+
blocks: (0, _burojs_core.snapshotBlockLookup)(snapshot),
|
|
276
|
+
frames: (0, _burojs_core.snapshotFrameLookup)(snapshot),
|
|
277
|
+
appFrames: (0, _burojs_core.snapshotAppFrames)(snapshot)
|
|
278
|
+
}), [page, snapshot]);
|
|
279
|
+
const failure = (0, _burojs_core.isScreenFailure)(compiled) ? compiled : null;
|
|
280
|
+
const screen = failure === null && !(0, _burojs_core.isScreenFailure)(compiled) ? compiled : null;
|
|
281
|
+
const caps = (0, _burojs_react.useI18nCaps)();
|
|
282
|
+
const name = canonicalNavKey(page);
|
|
283
|
+
const label = (0, _burojs_core.resolveNavLabel)(caps, `pages.${name}`, page.label);
|
|
284
|
+
(0, react.useEffect)(() => {
|
|
285
|
+
if (failure === null || process.env.NODE_ENV === "production") return;
|
|
286
|
+
console.error(`[burojs] The page "${name}" cannot render its body: ${failure.code} at ${failure.path.join(".")}: ${failure.message}.`);
|
|
287
|
+
}, [failure, name]);
|
|
288
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
289
|
+
className: "flex flex-col gap-4",
|
|
290
|
+
children: [(0, react_jsx_runtime.jsx)("h1", {
|
|
291
|
+
className: "text-2xl font-semibold tracking-tight",
|
|
292
|
+
children: label
|
|
293
|
+
}), screen === null ? (0, react_jsx_runtime.jsx)(RecordState, { state: "error" }) : (0, react_jsx_runtime.jsx)(PageRouteScreen, { children: (0, react_jsx_runtime.jsx)(PlannedBody, {
|
|
294
|
+
screen,
|
|
295
|
+
name,
|
|
296
|
+
label
|
|
297
|
+
}) })]
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function PageRouteScreen({ children }) {
|
|
301
|
+
const tabs = require_place_node.useTabsUrlState("show", true);
|
|
302
|
+
return (0, react_jsx_runtime.jsx)(require_place_node.RouteScreenClaimContext.Provider, {
|
|
303
|
+
value: false,
|
|
304
|
+
children: (0, react_jsx_runtime.jsx)(require_place_node.RouteScreenContext.Provider, {
|
|
305
|
+
value: true,
|
|
306
|
+
children: (0, react_jsx_runtime.jsx)(_burojs_react.ScreenStateProvider, {
|
|
307
|
+
open: tabs.open,
|
|
308
|
+
onOpenChange: tabs.onOpenChange,
|
|
309
|
+
children
|
|
310
|
+
})
|
|
311
|
+
})
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
function PlannedBody({ screen, name, label }) {
|
|
315
|
+
const page = (0, _burojs_react.usePageScreen)(screen);
|
|
316
|
+
const { PlacementScope } = page;
|
|
317
|
+
const route = (0, react.useContext)(_burojs_react.RouteSourceContext);
|
|
318
|
+
const catalog = (0, _burojs_react.useFrameImplementations)();
|
|
319
|
+
const id = (0, _burojs_core.formatEntityRef)(screen.frame);
|
|
320
|
+
const resolved = (0, react.useMemo)(() => require_place_node.resolveFrameView(screen.frame, catalog, "page"), [screen.frame, catalog]);
|
|
321
|
+
const t = (0, _burojs_react.useFrameT)(id);
|
|
322
|
+
const slots = {};
|
|
323
|
+
for (const slot of Object.keys(screen.slots)) slots[slot] = (page.plan.slots[slot] ?? []).every((node) => node.state === "absent") ? null : (0, react_jsx_runtime.jsx)(require_place_node.ScreenTree, {
|
|
324
|
+
region: slot === "body" ? "page-body" : "record",
|
|
325
|
+
slot,
|
|
326
|
+
resource: name,
|
|
327
|
+
mode: "show",
|
|
328
|
+
compiled: screen,
|
|
329
|
+
page
|
|
330
|
+
}, slot);
|
|
331
|
+
const View = resolved.view;
|
|
332
|
+
const viewProps = {
|
|
333
|
+
slots,
|
|
334
|
+
label,
|
|
335
|
+
params: route ?? {},
|
|
336
|
+
t
|
|
337
|
+
};
|
|
338
|
+
return (0, react_jsx_runtime.jsx)(_burojs_react.RouteSourceContext.Provider, {
|
|
339
|
+
value: null,
|
|
340
|
+
children: (0, react_jsx_runtime.jsx)(PlacementScope, { children: (0, react_jsx_runtime.jsx)(require_place_node.DeepLinkScope, {
|
|
341
|
+
plan: page.plan,
|
|
342
|
+
enabled: true,
|
|
343
|
+
children: (0, react_jsx_runtime.jsx)("div", {
|
|
344
|
+
"data-buro-region": "page-body",
|
|
345
|
+
children: (0, react_jsx_runtime.jsx)(react.Suspense, {
|
|
346
|
+
fallback: null,
|
|
347
|
+
children: (0, react_jsx_runtime.jsx)(View, { ...viewProps })
|
|
348
|
+
})
|
|
349
|
+
})
|
|
350
|
+
}) })
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
//#endregion
|
|
354
|
+
//#region src/resource/form-context.tsx
|
|
355
|
+
const FormControlContext = (0, react.createContext)(null);
|
|
356
|
+
const FormControlProvider = FormControlContext.Provider;
|
|
357
|
+
function useFormControl() {
|
|
358
|
+
return (0, react.useContext)(FormControlContext);
|
|
359
|
+
}
|
|
360
|
+
//#endregion
|
|
361
|
+
//#region src/frames/obligations.tsx
|
|
362
|
+
const SlotMountsContext = (0, react.createContext)(null);
|
|
363
|
+
function useSlotOnce(slot) {
|
|
364
|
+
const mounts = (0, react.useContext)(SlotMountsContext);
|
|
365
|
+
const token = (0, react.useId)();
|
|
366
|
+
const first = mounts === null || mounts.claim(slot, token);
|
|
367
|
+
(0, react.useEffect)(() => {
|
|
368
|
+
if (mounts === null) return;
|
|
369
|
+
mounts.claim(slot, token);
|
|
370
|
+
return () => mounts.release(slot, token);
|
|
371
|
+
}, [
|
|
372
|
+
mounts,
|
|
373
|
+
slot,
|
|
374
|
+
token
|
|
375
|
+
]);
|
|
376
|
+
(0, react.useEffect)(() => {
|
|
377
|
+
if (!first) mounts?.twice(slot);
|
|
378
|
+
}, [
|
|
379
|
+
first,
|
|
380
|
+
mounts,
|
|
381
|
+
slot
|
|
382
|
+
]);
|
|
383
|
+
return first;
|
|
384
|
+
}
|
|
385
|
+
function FrameObligations({ id, mode, showActions, root, children }) {
|
|
386
|
+
const told = (0, react.useRef)(new Set());
|
|
387
|
+
const counts = (0, react.useRef)(new Map());
|
|
388
|
+
const owners = (0, react.useRef)(new Map());
|
|
389
|
+
const tell = (key, write) => {
|
|
390
|
+
if (told.current.has(key) || process.env.NODE_ENV === "production") return;
|
|
391
|
+
told.current.add(key);
|
|
392
|
+
write();
|
|
393
|
+
};
|
|
394
|
+
const latestId = (0, react.useRef)(id);
|
|
395
|
+
latestId.current = id;
|
|
396
|
+
const slotMounts = (0, react.useMemo)(() => ({
|
|
397
|
+
claim(slot, token) {
|
|
398
|
+
const owner = owners.current.get(slot);
|
|
399
|
+
if (owner === void 0) {
|
|
400
|
+
owners.current.set(slot, token);
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
return owner === token;
|
|
404
|
+
},
|
|
405
|
+
release(slot, token) {
|
|
406
|
+
if (owners.current.get(slot) === token) owners.current.delete(slot);
|
|
407
|
+
},
|
|
408
|
+
twice(slot) {
|
|
409
|
+
tell(`slot-twice:${slot}`, () => console.error(`[burojs] frame.slot-twice: ${latestId.current} renders slots.${slot} twice; a slot renders at most once (body twice would mount every field twice under one form).`));
|
|
410
|
+
}
|
|
411
|
+
}), []);
|
|
412
|
+
const pieces = (0, react.useMemo)(() => ({
|
|
413
|
+
stub: false,
|
|
414
|
+
mount(piece) {
|
|
415
|
+
counts.current.set(piece, (counts.current.get(piece) ?? 0) + 1);
|
|
416
|
+
return () => counts.current.set(piece, (counts.current.get(piece) ?? 1) - 1);
|
|
417
|
+
}
|
|
418
|
+
}), []);
|
|
419
|
+
return (0, react_jsx_runtime.jsx)(SlotMountsContext.Provider, {
|
|
420
|
+
value: slotMounts,
|
|
421
|
+
children: (0, react_jsx_runtime.jsxs)(_burojs_react.FrameCheckContext.Provider, {
|
|
422
|
+
value: pieces,
|
|
423
|
+
children: [children, (0, react_jsx_runtime.jsx)(ObligationCheck, { check: () => {
|
|
424
|
+
const mounted = (piece) => (counts.current.get(piece) ?? 0) > 0;
|
|
425
|
+
if (mode !== "show" && !mounted("FormActions")) tell("no-form-actions", () => console.error(`[burojs] frame.no-form-actions: ${id} renders no <FormActions/> in ${mode}; nothing can submit or cancel the form.`));
|
|
426
|
+
if (mode === "show" && showActions && !mounted("RecordActions")) tell("no-actions", () => console.warn(`[burojs] screen.no-actions: ${id} renders no <RecordActions/> in show, and the resource has item actions there.`));
|
|
427
|
+
if (root.current?.querySelector("h1") != null) tell("h1", () => console.error(`[burojs] ${id} renders an h1; the route draws the screen's one h1.`));
|
|
428
|
+
} })]
|
|
429
|
+
})
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
function ObligationCheck({ check }) {
|
|
433
|
+
(0, react.useEffect)(() => {
|
|
434
|
+
check();
|
|
435
|
+
});
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
//#endregion
|
|
439
|
+
//#region src/frames/slot-wrapper.tsx
|
|
440
|
+
function DraftBanner() {
|
|
441
|
+
const { resource, mode, controller } = (0, _burojs_react.useRecordFrame)();
|
|
442
|
+
const t = (0, _burojs_react.useOptionalT)();
|
|
443
|
+
const tid = (0, _burojs_react.useTestIds)();
|
|
444
|
+
const { draft } = controller;
|
|
445
|
+
if (mode === "show" || draft.pending === null) return null;
|
|
446
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
447
|
+
"data-testid": tid.formDraftBanner(resource, mode),
|
|
448
|
+
role: "status",
|
|
449
|
+
"aria-live": "polite",
|
|
450
|
+
className: "flex flex-wrap items-center gap-2 rounded-md border border-warning/50 bg-warning/10 px-3 py-2 text-sm",
|
|
451
|
+
children: [
|
|
452
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
453
|
+
className: "font-medium",
|
|
454
|
+
children: draft.recordChanged ? t("draft.recordChanged") : t("draft.found", { params: { time: timeAgo(draft.pending.savedAt, t) } })
|
|
455
|
+
}),
|
|
456
|
+
draft.recordChanged ? null : (0, react_jsx_runtime.jsx)("span", {
|
|
457
|
+
className: "text-muted-foreground",
|
|
458
|
+
children: t("draft.restorePrompt")
|
|
459
|
+
}),
|
|
460
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
461
|
+
className: "ml-auto flex gap-2",
|
|
462
|
+
children: [draft.recordChanged ? null : (0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
463
|
+
type: "button",
|
|
464
|
+
size: "sm",
|
|
465
|
+
variant: "outline",
|
|
466
|
+
"data-testid": tid.formDraftRestore(resource, mode),
|
|
467
|
+
onClick: draft.restore,
|
|
468
|
+
children: t("draft.restore")
|
|
469
|
+
}), (0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
470
|
+
type: "button",
|
|
471
|
+
size: "sm",
|
|
472
|
+
variant: "ghost",
|
|
473
|
+
"data-testid": tid.formDraftDiscard(resource, mode),
|
|
474
|
+
onClick: draft.discard,
|
|
475
|
+
children: t("draft.discard")
|
|
476
|
+
})]
|
|
477
|
+
})
|
|
478
|
+
]
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
function timeAgo(savedAt, t) {
|
|
482
|
+
const then = new Date(savedAt).getTime();
|
|
483
|
+
if (!Number.isFinite(then)) return t("draft.timeAgoRecently");
|
|
484
|
+
const diff = Date.now() - then;
|
|
485
|
+
if (diff < 6e4) return t("draft.timeAgoMoment");
|
|
486
|
+
if (diff < 36e5) return t("draft.timeAgoMinutes", { count: Math.round(diff / 6e4) });
|
|
487
|
+
if (diff < 864e5) return t("draft.timeAgoHours", { count: Math.round(diff / 36e5) });
|
|
488
|
+
return t("draft.timeAgoDays", { count: Math.round(diff / 864e5) });
|
|
489
|
+
}
|
|
490
|
+
function frameRecordId(frame) {
|
|
491
|
+
if (frame.recordId !== void 0) return frame.recordId;
|
|
492
|
+
const id = frame.record?.id;
|
|
493
|
+
return typeof id === "string" || typeof id === "number" ? String(id) : void 0;
|
|
494
|
+
}
|
|
495
|
+
function BodySlot() {
|
|
496
|
+
const frame = (0, _burojs_react.useRecordFrame)();
|
|
497
|
+
const { resource, mode, controller } = frame;
|
|
498
|
+
const chrome = require_place_node.useFrameChrome();
|
|
499
|
+
const tid = (0, _burojs_react.useTestIds)();
|
|
500
|
+
const id = frameRecordId(frame);
|
|
501
|
+
const tree = (0, react_jsx_runtime.jsx)("div", {
|
|
502
|
+
"data-buro-region": "record-body",
|
|
503
|
+
"data-buro-slot": _burojs_core.FORM_SLOT,
|
|
504
|
+
children: (0, react_jsx_runtime.jsx)(require_place_node.ScreenTree, {
|
|
505
|
+
region: "record",
|
|
506
|
+
slot: _burojs_core.FORM_SLOT,
|
|
507
|
+
resource,
|
|
508
|
+
mode,
|
|
509
|
+
compiled: controller.screen,
|
|
510
|
+
controller
|
|
511
|
+
})
|
|
512
|
+
});
|
|
513
|
+
if (mode === "show") return tree;
|
|
514
|
+
return (0, react_jsx_runtime.jsxs)("form", {
|
|
515
|
+
id: chrome.formId,
|
|
516
|
+
noValidate: true,
|
|
517
|
+
"data-testid": tid.form(resource, mode, id),
|
|
518
|
+
onSubmit: (event) => {
|
|
519
|
+
event.preventDefault();
|
|
520
|
+
const steps = controller.steps;
|
|
521
|
+
if (steps !== null && steps.current !== void 0 && steps.current !== steps.ids[steps.ids.length - 1]) steps.next();
|
|
522
|
+
else chrome.submit();
|
|
523
|
+
},
|
|
524
|
+
className: "flex flex-col gap-4",
|
|
525
|
+
children: [(0, react_jsx_runtime.jsx)(DraftBanner, {}), tree]
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
function DisplaySlot({ name }) {
|
|
529
|
+
const { resource, mode, controller } = (0, _burojs_react.useRecordFrame)();
|
|
530
|
+
return (0, react_jsx_runtime.jsx)(require_place_node.ScreenTree, {
|
|
531
|
+
region: "record",
|
|
532
|
+
slot: name,
|
|
533
|
+
resource,
|
|
534
|
+
mode,
|
|
535
|
+
compiled: controller.screen,
|
|
536
|
+
controller
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
function SlotWrapper({ name }) {
|
|
540
|
+
if (!useSlotOnce(name)) return null;
|
|
541
|
+
return name === _burojs_core.FORM_SLOT ? (0, react_jsx_runtime.jsx)(BodySlot, {}) : (0, react_jsx_runtime.jsx)(DisplaySlot, { name });
|
|
542
|
+
}
|
|
543
|
+
//#endregion
|
|
544
|
+
//#region src/frames/record-frame-host.tsx
|
|
545
|
+
const NO_ROOT = { current: null };
|
|
546
|
+
function drawnFields(roots, compiled, out) {
|
|
547
|
+
for (const node of roots) {
|
|
548
|
+
if (node.state === "absent") continue;
|
|
549
|
+
const field = node.field ?? compiled.nodes[node.index]?.field;
|
|
550
|
+
if (node.kind === "field" && field !== void 0 && node.state === "ready") out.add(field);
|
|
551
|
+
drawnFields(node.children, compiled, out);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function frameRecord(record, compiled, slots, idField, visible = () => true) {
|
|
555
|
+
if (record === void 0 || compiled === null || slots === void 0) return void 0;
|
|
556
|
+
const drawn = new Set();
|
|
557
|
+
for (const roots of Object.values(slots)) drawnFields(roots, compiled, drawn);
|
|
558
|
+
const declarations = new Map(compiled.fields.map((entry) => [entry.name, entry.declaration]));
|
|
559
|
+
const out = {};
|
|
560
|
+
if (Object.hasOwn(record, idField)) out[idField] = record[idField];
|
|
561
|
+
for (const name of drawn) {
|
|
562
|
+
if (!Object.hasOwn(record, name) || !visible(name)) continue;
|
|
563
|
+
if ((0, _burojs_core.fieldSensitivity)(declarations.get(name)) !== void 0) continue;
|
|
564
|
+
out[name] = record[name];
|
|
565
|
+
}
|
|
566
|
+
return Object.freeze(out);
|
|
567
|
+
}
|
|
568
|
+
function declaredSlots(compiled) {
|
|
569
|
+
if (compiled === null) return [];
|
|
570
|
+
return Object.entries(compiled.slots).filter(([, nodes]) => nodes.length > 0).map(([name]) => name);
|
|
571
|
+
}
|
|
572
|
+
function useEffectiveFrame(instance) {
|
|
573
|
+
const { resource, mode, controller } = (0, _burojs_react.useRecordFrame)();
|
|
574
|
+
const compiled = controller.screen;
|
|
575
|
+
const missing = (0, react.useMemo)(() => {
|
|
576
|
+
if (instance === void 0 || instance.for !== "record") return void 0;
|
|
577
|
+
const own = new Set(Object.keys(instance.slots));
|
|
578
|
+
return declaredSlots(compiled).find((slot) => !own.has(slot));
|
|
579
|
+
}, [instance, compiled]);
|
|
580
|
+
const told = (0, react.useRef)(false);
|
|
581
|
+
const instanceId = instance === void 0 ? void 0 : (0, _burojs_core.formatEntityRef)(instance.ref);
|
|
582
|
+
(0, react.useEffect)(() => {
|
|
583
|
+
if (missing === void 0 || told.current || process.env.NODE_ENV === "production") return;
|
|
584
|
+
told.current = true;
|
|
585
|
+
console.error(`[burojs] <RecordScreen frame={${instanceId}}> lacks the slot ${missing} that ${resource}'s ${mode} screen fills; the screen's own frame draws it.`);
|
|
586
|
+
}, [
|
|
587
|
+
missing,
|
|
588
|
+
instanceId,
|
|
589
|
+
resource,
|
|
590
|
+
mode
|
|
591
|
+
]);
|
|
592
|
+
if (instance !== void 0 && instance.for === "record" && missing === void 0 && compiled !== null) return {
|
|
593
|
+
ref: instance.ref,
|
|
594
|
+
slotNames: Object.keys(instance.slots)
|
|
595
|
+
};
|
|
596
|
+
return {
|
|
597
|
+
ref: compiled?.frame ?? _burojs_core.builtinFrames.record.ref,
|
|
598
|
+
slotNames: compiled === null ? ["header", _burojs_core.FORM_SLOT] : Object.keys(compiled.slots)
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
function RecordFrameView({ frame: instance }) {
|
|
602
|
+
const { resource, mode, controller, record } = (0, _burojs_react.useRecordFrame)();
|
|
603
|
+
const chrome = require_place_node.useFrameChrome();
|
|
604
|
+
const quick = (0, _burojs_react.useQuickEntry)();
|
|
605
|
+
const app = (0, _burojs_react.useApp)();
|
|
606
|
+
const catalog = (0, _burojs_react.useFrameImplementations)();
|
|
607
|
+
const { ref, slotNames } = useEffectiveFrame(instance);
|
|
608
|
+
const id = (0, _burojs_core.formatEntityRef)(ref);
|
|
609
|
+
const resolved = (0, react.useMemo)(() => require_place_node.resolveFrameView(ref, catalog, "record"), [ref, catalog]);
|
|
610
|
+
const t = (0, _burojs_react.useFrameT)(id);
|
|
611
|
+
const compiled = controller.screen;
|
|
612
|
+
const plan = controller.plan;
|
|
613
|
+
const slots = {};
|
|
614
|
+
for (const name of slotNames) {
|
|
615
|
+
if (name === _burojs_core.FORM_SLOT) {
|
|
616
|
+
slots[name] = (0, react_jsx_runtime.jsx)(SlotWrapper, { name }, name);
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
slots[name] = (plan?.slots[name] ?? []).every((node) => node.state === "absent") ? null : (0, react_jsx_runtime.jsx)(SlotWrapper, { name }, name);
|
|
620
|
+
}
|
|
621
|
+
slots[_burojs_core.FORM_SLOT] ??= (0, react_jsx_runtime.jsx)(SlotWrapper, { name: _burojs_core.FORM_SLOT });
|
|
622
|
+
const idField = app.registry.get(resource)?.data?.idField ?? "id";
|
|
623
|
+
const access = (0, _burojs_react.useFieldAccess)(resource, "record", record === void 0 ? void 0 : (0, _burojs_react.policyRecordOf)(controller, record));
|
|
624
|
+
const projected = (0, react.useMemo)(() => frameRecord(record, compiled, plan?.slots, idField, (name) => access[name]?.view === "visible"), [
|
|
625
|
+
record,
|
|
626
|
+
compiled,
|
|
627
|
+
plan,
|
|
628
|
+
idField,
|
|
629
|
+
access
|
|
630
|
+
]);
|
|
631
|
+
const quickHost = chrome.hostTitle === true && quick !== null;
|
|
632
|
+
const props = {
|
|
633
|
+
slots,
|
|
634
|
+
host: quickHost ? quick.host : "page",
|
|
635
|
+
mode,
|
|
636
|
+
screen: {
|
|
637
|
+
resource,
|
|
638
|
+
entry: quickHost ? "quick" : "route"
|
|
639
|
+
},
|
|
640
|
+
record: mode === "create" ? void 0 : projected,
|
|
641
|
+
t
|
|
642
|
+
};
|
|
643
|
+
const policyRecord = (0, _burojs_react.policyRecordOf)(controller, record);
|
|
644
|
+
const rowId = record?.[idField];
|
|
645
|
+
const actionRow = (0, react.useMemo)(() => ({
|
|
646
|
+
...policyRecord ?? {},
|
|
647
|
+
id: typeof rowId === "string" || typeof rowId === "number" ? String(rowId) : ""
|
|
648
|
+
}), [policyRecord, rowId]);
|
|
649
|
+
const showItemActions = require_place_node.useShownItemActions(resource, actionRow, "show");
|
|
650
|
+
const showActions = mode === "show" && record !== void 0 && showItemActions.length > 0;
|
|
651
|
+
const View = resolved.view;
|
|
652
|
+
const builtin = require_place_node.isBuiltinRecordView(resolved);
|
|
653
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [builtin ? null : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(require_place_node.HeaderTypeNotice, { resource }), (0, react_jsx_runtime.jsx)(require_place_node.ChangeTypeButton, {})] }), (0, react_jsx_runtime.jsx)(require_place_node.TypeChromeDrawnContext.Provider, {
|
|
654
|
+
value: !builtin,
|
|
655
|
+
children: (0, react_jsx_runtime.jsx)(react.Suspense, {
|
|
656
|
+
fallback: (0, react_jsx_runtime.jsx)(RecordState, { state: "loading" }),
|
|
657
|
+
children: (0, react_jsx_runtime.jsx)(FrameObligations, {
|
|
658
|
+
id,
|
|
659
|
+
mode,
|
|
660
|
+
showActions,
|
|
661
|
+
root: chrome.root ?? NO_ROOT,
|
|
662
|
+
children: (0, react_jsx_runtime.jsx)(View, { ...props })
|
|
663
|
+
})
|
|
664
|
+
})
|
|
665
|
+
})] });
|
|
666
|
+
}
|
|
667
|
+
//#endregion
|
|
668
|
+
//#region src/record/type-offer.tsx
|
|
669
|
+
function TypeOffer({ resource, offer, onChoose }) {
|
|
670
|
+
const app = (0, _burojs_react.useApp)();
|
|
671
|
+
const t = (0, _burojs_react.useOptionalT)();
|
|
672
|
+
const tid = (0, _burojs_react.useTestIds)();
|
|
673
|
+
const def = app.registry.get(resource);
|
|
674
|
+
const by = def?.types?.by;
|
|
675
|
+
const field = by === void 0 ? void 0 : def?.fields[by];
|
|
676
|
+
const ref = def?.ref ?? {
|
|
677
|
+
namespace: "default",
|
|
678
|
+
id: resource
|
|
679
|
+
};
|
|
680
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
681
|
+
"aria-labelledby": "type-offer-title",
|
|
682
|
+
className: "flex flex-col gap-3",
|
|
683
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
684
|
+
className: "flex flex-col gap-1",
|
|
685
|
+
children: [(0, react_jsx_runtime.jsx)("h2", {
|
|
686
|
+
id: "type-offer-title",
|
|
687
|
+
className: "text-base font-semibold",
|
|
688
|
+
children: t("create.typeOffer.title", { defaultValue: "Choose a type" })
|
|
689
|
+
}), (0, react_jsx_runtime.jsx)("p", {
|
|
690
|
+
className: "text-sm text-muted-foreground",
|
|
691
|
+
children: t("create.typeOffer.hint", { defaultValue: "The type decides which fields the record has." })
|
|
692
|
+
})]
|
|
693
|
+
}), offer.status === "pending" ? (0, react_jsx_runtime.jsxs)("div", {
|
|
694
|
+
"aria-busy": "true",
|
|
695
|
+
className: "flex flex-col gap-2",
|
|
696
|
+
children: [(0, react_jsx_runtime.jsx)(require_skeleton.Skeleton, { className: "h-10 w-full" }), (0, react_jsx_runtime.jsx)(require_skeleton.Skeleton, { className: "h-10 w-full" })]
|
|
697
|
+
}) : offer.types.length === 0 ? (0, react_jsx_runtime.jsx)("p", {
|
|
698
|
+
role: "status",
|
|
699
|
+
className: "text-sm text-muted-foreground",
|
|
700
|
+
children: t("create.typeOffer.empty", { defaultValue: "No type can be created here." })
|
|
701
|
+
}) : (0, react_jsx_runtime.jsx)("ul", {
|
|
702
|
+
className: "grid grid-cols-1 gap-2 sm:grid-cols-2",
|
|
703
|
+
children: offer.types.map((type) => (0, react_jsx_runtime.jsx)("li", { children: (0, react_jsx_runtime.jsx)("button", {
|
|
704
|
+
type: "button",
|
|
705
|
+
"data-testid": tid.typeOffer(resource, type),
|
|
706
|
+
onClick: () => onChoose(type),
|
|
707
|
+
className: "flex w-full items-center gap-2 rounded-md border px-3 py-2 text-left text-sm hover:bg-muted focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
|
|
708
|
+
children: by === void 0 || field === void 0 ? type : (0, react_jsx_runtime.jsx)(require_place_node.FieldDisplay, {
|
|
709
|
+
resource: ref,
|
|
710
|
+
name: by,
|
|
711
|
+
field,
|
|
712
|
+
value: type,
|
|
713
|
+
fallback: (0, react_jsx_runtime.jsx)("span", { children: type })
|
|
714
|
+
})
|
|
715
|
+
}) }, type))
|
|
716
|
+
})]
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
//#endregion
|
|
720
|
+
//#region src/record/record-screen.tsx
|
|
721
|
+
function isAbort(error) {
|
|
722
|
+
if (error === null || typeof error !== "object" && typeof error !== "function") return false;
|
|
723
|
+
try {
|
|
724
|
+
return error.name === "AbortError";
|
|
725
|
+
} catch {
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
function canonicalName(resource) {
|
|
730
|
+
const ref = (0, _burojs_core.normalizeEntityRef)(typeof resource === "string" ? resource : resource.ref);
|
|
731
|
+
return (0, _burojs_core.canonicalEntityNodeKey)({
|
|
732
|
+
name: ref.id,
|
|
733
|
+
ref
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
function frameId(frame) {
|
|
737
|
+
return frameRecordId(frame);
|
|
738
|
+
}
|
|
739
|
+
function RecordFrameHost(props) {
|
|
740
|
+
const { resource, mode } = props;
|
|
741
|
+
const controller = (0, _burojs_react.useFormController)({
|
|
742
|
+
resource,
|
|
743
|
+
mode,
|
|
744
|
+
...props.recordId === void 0 ? {} : { recordId: props.recordId },
|
|
745
|
+
...props.type === void 0 ? {} : { type: props.type },
|
|
746
|
+
...props.record === void 0 ? {} : { record: props.record },
|
|
747
|
+
...props.defaults === void 0 ? {} : { defaults: props.defaults },
|
|
748
|
+
...props.onSubmit === void 0 ? {} : { onSubmit: props.onSubmit },
|
|
749
|
+
...props.hostTitle ? { entry: "quick" } : {}
|
|
750
|
+
});
|
|
751
|
+
const inputApplied = (0, react.useRef)(false);
|
|
752
|
+
const { input } = props;
|
|
753
|
+
const plan = controller.plan;
|
|
754
|
+
(0, react.useEffect)(() => {
|
|
755
|
+
if (inputApplied.current || input === void 0 || mode !== "create" || plan === null) return;
|
|
756
|
+
inputApplied.current = true;
|
|
757
|
+
for (const [key, value] of Object.entries(input)) if (plan.fields[key] === "active") controller.setValue(key, value);
|
|
758
|
+
}, [
|
|
759
|
+
input,
|
|
760
|
+
mode,
|
|
761
|
+
plan,
|
|
762
|
+
controller
|
|
763
|
+
]);
|
|
764
|
+
const tid = (0, _burojs_react.useTestIds)();
|
|
765
|
+
const formId = `record-form-${(0, react.useId)()}`;
|
|
766
|
+
const root = (0, react.useRef)(null);
|
|
767
|
+
const [submitError, setSubmitError] = (0, react.useState)(void 0);
|
|
768
|
+
const latest = (0, react.useRef)({
|
|
769
|
+
controller,
|
|
770
|
+
onSuccess: props.onSuccess
|
|
771
|
+
});
|
|
772
|
+
latest.current = {
|
|
773
|
+
controller,
|
|
774
|
+
onSuccess: props.onSuccess
|
|
775
|
+
};
|
|
776
|
+
const mounted = (0, react.useRef)(true);
|
|
777
|
+
(0, react.useEffect)(() => {
|
|
778
|
+
mounted.current = true;
|
|
779
|
+
return () => {
|
|
780
|
+
mounted.current = false;
|
|
781
|
+
};
|
|
782
|
+
}, []);
|
|
783
|
+
const submit = (0, react.useCallback)(async () => {
|
|
784
|
+
setSubmitError(void 0);
|
|
785
|
+
const outcome = await latest.current.controller.submit();
|
|
786
|
+
if (!mounted.current) return;
|
|
787
|
+
if (outcome.status === "submitted") latest.current.onSuccess?.(outcome.result);
|
|
788
|
+
else if (outcome.status === "failed" && !isAbort(outcome.error)) setSubmitError(outcome.error ?? null);
|
|
789
|
+
}, []);
|
|
790
|
+
const record = controller.record;
|
|
791
|
+
const frame = (0, react.useMemo)(() => ({
|
|
792
|
+
resource,
|
|
793
|
+
recordId: props.recordId,
|
|
794
|
+
mode,
|
|
795
|
+
record,
|
|
796
|
+
controller
|
|
797
|
+
}), [
|
|
798
|
+
resource,
|
|
799
|
+
props.recordId,
|
|
800
|
+
mode,
|
|
801
|
+
record,
|
|
802
|
+
controller
|
|
803
|
+
]);
|
|
804
|
+
const { chooseType } = props;
|
|
805
|
+
const typedAndAccepted = mode === "create" && controller.typeOffer !== null && controller.failure === null;
|
|
806
|
+
const routeScreen = require_place_node.useIsRouteScreen();
|
|
807
|
+
const adapter = (0, _burojs_react.useOptionalRouterAdapter)();
|
|
808
|
+
const hostRouter = routeScreen ? adapter : null;
|
|
809
|
+
const guardText = (0, _burojs_react.useNavigationGuardText)();
|
|
810
|
+
const changeType = (0, react.useMemo)(() => typedAndAccepted ? () => {
|
|
811
|
+
if (hostRouter === null && latest.current.controller.isDirty && !window.confirm(`${guardText.title}\n\n${guardText.body}`)) return;
|
|
812
|
+
chooseType(void 0);
|
|
813
|
+
} : void 0, [
|
|
814
|
+
typedAndAccepted,
|
|
815
|
+
chooseType,
|
|
816
|
+
hostRouter,
|
|
817
|
+
guardText.title,
|
|
818
|
+
guardText.body
|
|
819
|
+
]);
|
|
820
|
+
const typesDecl = (0, _burojs_react.useApp)().registry.get(resource)?.types;
|
|
821
|
+
const loaded = controller.record;
|
|
822
|
+
const unsupportedType = (0, react.useMemo)(() => {
|
|
823
|
+
if (mode === "create" || typesDecl === void 0 || loaded === void 0) return void 0;
|
|
824
|
+
const value = loaded[typesDecl.by];
|
|
825
|
+
const name = typeof value === "string" && value !== "" ? value : null;
|
|
826
|
+
return name !== null && typesDecl.items.some((item) => item.name === name) ? void 0 : name;
|
|
827
|
+
}, [
|
|
828
|
+
mode,
|
|
829
|
+
typesDecl,
|
|
830
|
+
loaded
|
|
831
|
+
]);
|
|
832
|
+
const chrome = (0, react.useMemo)(() => ({
|
|
833
|
+
...props.onCancel === void 0 ? {} : { onCancel: props.onCancel },
|
|
834
|
+
...props.submitLabel === void 0 ? {} : { submitLabel: props.submitLabel },
|
|
835
|
+
...props.extraActions === void 0 ? {} : { extraActions: props.extraActions },
|
|
836
|
+
...changeType === void 0 ? {} : { changeType },
|
|
837
|
+
...unsupportedType === void 0 ? {} : { unsupportedType },
|
|
838
|
+
...props.hostTitle ? { hostTitle: true } : {},
|
|
839
|
+
formId,
|
|
840
|
+
submitError,
|
|
841
|
+
submit,
|
|
842
|
+
root
|
|
843
|
+
}), [
|
|
844
|
+
props.onCancel,
|
|
845
|
+
props.submitLabel,
|
|
846
|
+
props.extraActions,
|
|
847
|
+
changeType,
|
|
848
|
+
unsupportedType,
|
|
849
|
+
props.hostTitle,
|
|
850
|
+
formId,
|
|
851
|
+
submitError,
|
|
852
|
+
submit
|
|
853
|
+
]);
|
|
854
|
+
(0, _burojs_react.usePublishDocVariant)(mode === "show" ? resource : void 0, record);
|
|
855
|
+
const failure = controller.failure;
|
|
856
|
+
const offering = failure?.kind === "type-required" && controller.typeOffer !== null;
|
|
857
|
+
(0, react.useEffect)(() => {
|
|
858
|
+
if (failure === null || offering || process.env.NODE_ENV === "production") return;
|
|
859
|
+
console.error(`[burojs] <RecordScreen resource="${resource}" mode="${mode}"> cannot render its screen: ${failure.kind === "screen" ? `${failure.failure.code} at ${failure.failure.path.join(".")}: ${failure.failure.message}` : "a typed create needs an accepted type"}.`);
|
|
860
|
+
}, [
|
|
861
|
+
failure,
|
|
862
|
+
offering,
|
|
863
|
+
resource,
|
|
864
|
+
mode
|
|
865
|
+
]);
|
|
866
|
+
const setValues = (0, react.useCallback)((patch) => {
|
|
867
|
+
for (const [name, value] of Object.entries(patch)) latest.current.controller.setValue(name, value);
|
|
868
|
+
}, []);
|
|
869
|
+
const formControl = (0, react.useMemo)(() => ({ setValues }), [setValues]);
|
|
870
|
+
const id = frameId(frame);
|
|
871
|
+
const rootId = mode === "show" ? tid.pageShow(resource, id ?? "") : mode === "edit" ? tid.pageEdit(resource, id ?? "") : tid.pageCreate(resource);
|
|
872
|
+
return (0, react_jsx_runtime.jsx)(_burojs_react.RouteSourceContext.Provider, {
|
|
873
|
+
value: null,
|
|
874
|
+
children: (0, react_jsx_runtime.jsx)(_burojs_react.RecordFrameContext.Provider, {
|
|
875
|
+
value: frame,
|
|
876
|
+
children: (0, react_jsx_runtime.jsx)(require_place_node.FrameChromeContext.Provider, {
|
|
877
|
+
value: chrome,
|
|
878
|
+
children: (0, react_jsx_runtime.jsxs)(FormControlProvider, {
|
|
879
|
+
value: formControl,
|
|
880
|
+
children: [(0, react_jsx_runtime.jsx)(require_place_node.DeepLinkScope, {
|
|
881
|
+
plan: controller.plan,
|
|
882
|
+
enabled: routeScreen && mode !== "create",
|
|
883
|
+
children: (0, react_jsx_runtime.jsx)("div", {
|
|
884
|
+
ref: root,
|
|
885
|
+
"data-testid": rootId,
|
|
886
|
+
className: "flex flex-col gap-[var(--buro-section-gap)]",
|
|
887
|
+
children: offering && controller.typeOffer !== null ? (0, react_jsx_runtime.jsx)(TypeOffer, {
|
|
888
|
+
resource,
|
|
889
|
+
offer: controller.typeOffer,
|
|
890
|
+
onChoose: (type) => chooseType(type)
|
|
891
|
+
}) : failure !== null ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(RecordState, { state: "error" }), (0, react_jsx_runtime.jsx)(require_place_node.FormActions, {})] }) : mode === "edit" && unsupportedType !== void 0 ? (0, react_jsx_runtime.jsx)(require_place_node.UndeclaredTypeNotice, {
|
|
892
|
+
resource,
|
|
893
|
+
typeValue: unsupportedType
|
|
894
|
+
}) : (0, react_jsx_runtime.jsx)(RecordFrameView, { ...props.frame === void 0 ? {} : { frame: props.frame } })
|
|
895
|
+
})
|
|
896
|
+
}), controller.checkGate === null ? null : (0, react_jsx_runtime.jsx)(require_check_confirm_dialog.CheckConfirmDialog, {
|
|
897
|
+
state: controller.checkGate,
|
|
898
|
+
testid: `${tid.form(resource, mode, id)}:checks`
|
|
899
|
+
})]
|
|
900
|
+
})
|
|
901
|
+
})
|
|
902
|
+
})
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
function RecordScreen(props) {
|
|
906
|
+
const app = (0, _burojs_react.useApp)();
|
|
907
|
+
const routeScreen = (0, react.useContext)(require_place_node.RouteScreenClaimContext);
|
|
908
|
+
const quickScreen = (0, react.useContext)(require_place_node.QuickScreenClaimContext);
|
|
909
|
+
const route = (0, react.useContext)(_burojs_react.RouteSourceContext);
|
|
910
|
+
const resource = canonicalName(props.resource);
|
|
911
|
+
const tabs = require_place_node.useTabsUrlState(props.mode, routeScreen);
|
|
912
|
+
const adapter = (0, _burojs_react.useOptionalRouterAdapter)();
|
|
913
|
+
const router = routeScreen ? adapter : null;
|
|
914
|
+
const navigate = (0, _burojs_react.useRouteNavigate)();
|
|
915
|
+
const [local, setLocal] = (0, react.useState)(props.type);
|
|
916
|
+
const [typeProp, setTypeProp] = (0, react.useState)(props.type);
|
|
917
|
+
if (props.type !== typeProp) {
|
|
918
|
+
setTypeProp(props.type);
|
|
919
|
+
setLocal(props.type);
|
|
920
|
+
}
|
|
921
|
+
const [chose, setChose] = (0, react.useState)(false);
|
|
922
|
+
const fromUrl = router?.searchParams.get("type") ?? void 0;
|
|
923
|
+
const type = props.mode !== "create" ? props.type : router === null ? local : chose ? fromUrl : props.type ?? fromUrl;
|
|
924
|
+
const chooseType = (0, react.useCallback)((next) => {
|
|
925
|
+
if (router === null) {
|
|
926
|
+
setLocal(next);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
setChose(true);
|
|
930
|
+
const params = new URLSearchParams(router.searchParams);
|
|
931
|
+
if (next === void 0) params.delete("type");
|
|
932
|
+
else params.set("type", next);
|
|
933
|
+
const query = params.toString();
|
|
934
|
+
navigate(query === "" ? router.pathname : `${router.pathname}?${query}`);
|
|
935
|
+
}, [router, navigate]);
|
|
936
|
+
if (app.registry.get(resource) === void 0) return (0, react_jsx_runtime.jsx)(RecordState, { state: "not-found" });
|
|
937
|
+
const { type: _declared, ...rest } = props;
|
|
938
|
+
return (0, react_jsx_runtime.jsx)(require_place_node.RouteScreenClaimContext.Provider, {
|
|
939
|
+
value: false,
|
|
940
|
+
children: (0, react_jsx_runtime.jsx)(require_place_node.QuickScreenClaimContext.Provider, {
|
|
941
|
+
value: false,
|
|
942
|
+
children: (0, react_jsx_runtime.jsx)(_burojs_react.RouteSourceContext.Provider, {
|
|
943
|
+
value: routeScreen || quickScreen ? route : null,
|
|
944
|
+
children: (0, react_jsx_runtime.jsx)(require_place_node.RouteScreenContext.Provider, {
|
|
945
|
+
value: routeScreen,
|
|
946
|
+
children: (0, react_jsx_runtime.jsx)(_burojs_react.ResourceContext.Provider, {
|
|
947
|
+
value: { resource },
|
|
948
|
+
children: (0, react_jsx_runtime.jsx)(_burojs_react.ScreenStateProvider, {
|
|
949
|
+
open: tabs.open,
|
|
950
|
+
onOpenChange: tabs.onOpenChange,
|
|
951
|
+
children: (0, react_jsx_runtime.jsx)(RecordFrameHost, {
|
|
952
|
+
...rest,
|
|
953
|
+
...type === void 0 ? {} : { type },
|
|
954
|
+
resource,
|
|
955
|
+
chooseType,
|
|
956
|
+
hostTitle: quickScreen
|
|
957
|
+
}, type ?? "")
|
|
958
|
+
})
|
|
959
|
+
})
|
|
960
|
+
})
|
|
961
|
+
})
|
|
962
|
+
})
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
//#endregion
|
|
966
|
+
//#region src/layout/record-trail-store.ts
|
|
967
|
+
const stores = new WeakMap();
|
|
968
|
+
function storeOf(app) {
|
|
969
|
+
let store = stores.get(app);
|
|
970
|
+
if (store === void 0) {
|
|
971
|
+
store = {
|
|
972
|
+
current: null,
|
|
973
|
+
listeners: new Set()
|
|
974
|
+
};
|
|
975
|
+
stores.set(app, store);
|
|
976
|
+
}
|
|
977
|
+
return store;
|
|
978
|
+
}
|
|
979
|
+
function set(store, value) {
|
|
980
|
+
store.current = value;
|
|
981
|
+
for (const listener of store.listeners) listener();
|
|
982
|
+
}
|
|
983
|
+
function recordTrailKey(resource, id, route) {
|
|
984
|
+
return JSON.stringify([
|
|
985
|
+
resource,
|
|
986
|
+
id,
|
|
987
|
+
route
|
|
988
|
+
]);
|
|
989
|
+
}
|
|
990
|
+
function usePublishRecordTrail(entry) {
|
|
991
|
+
const app = (0, _burojs_react.useApp)();
|
|
992
|
+
const key = entry?.key;
|
|
993
|
+
const title = entry?.title;
|
|
994
|
+
const flowLabel = entry?.flowLabel;
|
|
995
|
+
(0, react.useEffect)(() => {
|
|
996
|
+
if (key === void 0 || title === void 0) return;
|
|
997
|
+
const store = storeOf(app);
|
|
998
|
+
const value = {
|
|
999
|
+
key,
|
|
1000
|
+
title,
|
|
1001
|
+
...flowLabel === void 0 ? {} : { flowLabel }
|
|
1002
|
+
};
|
|
1003
|
+
set(store, value);
|
|
1004
|
+
return () => {
|
|
1005
|
+
if (store.current === value) set(store, null);
|
|
1006
|
+
};
|
|
1007
|
+
}, [
|
|
1008
|
+
app,
|
|
1009
|
+
key,
|
|
1010
|
+
title,
|
|
1011
|
+
flowLabel
|
|
1012
|
+
]);
|
|
1013
|
+
}
|
|
1014
|
+
function usePublishedRecordTrail(key) {
|
|
1015
|
+
const store = storeOf((0, _burojs_react.useApp)());
|
|
1016
|
+
const subscribe = (0, react.useCallback)((listener) => {
|
|
1017
|
+
store.listeners.add(listener);
|
|
1018
|
+
return () => {
|
|
1019
|
+
store.listeners.delete(listener);
|
|
1020
|
+
};
|
|
1021
|
+
}, [store]);
|
|
1022
|
+
const current = (0, react.useSyncExternalStore)(subscribe, () => store.current, () => null);
|
|
1023
|
+
return current !== null && current.key === key ? current : null;
|
|
1024
|
+
}
|
|
1025
|
+
//#endregion
|
|
1026
|
+
//#region src/layout/route-trail.ts
|
|
1027
|
+
function useTrailContext() {
|
|
1028
|
+
const app = (0, _burojs_react.useApp)();
|
|
1029
|
+
const caps = (0, _burojs_react.useI18nCaps)();
|
|
1030
|
+
const isVisible = useNavVisible();
|
|
1031
|
+
const t = (0, _burojs_react.useT)();
|
|
1032
|
+
return {
|
|
1033
|
+
registry: app.registry,
|
|
1034
|
+
caps,
|
|
1035
|
+
isVisible,
|
|
1036
|
+
t
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function groupCrumbHref(registry, group, isVisible) {
|
|
1040
|
+
let href;
|
|
1041
|
+
try {
|
|
1042
|
+
href = (0, _burojs_core.navHrefFor)(registry, group, { isVisible });
|
|
1043
|
+
} catch {
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
return (0, _burojs_core.matchPath)(registry, href, { isVisible }) === null ? void 0 : href;
|
|
1047
|
+
}
|
|
1048
|
+
function groupCrumbs(ctx, chain) {
|
|
1049
|
+
return chain.map((group) => {
|
|
1050
|
+
const key = canonicalNavKey(group);
|
|
1051
|
+
return {
|
|
1052
|
+
label: (0, _burojs_core.resolveNavLabel)(ctx.caps, `groups.${key}`, group.label),
|
|
1053
|
+
href: groupCrumbHref(ctx.registry, key, ctx.isVisible)
|
|
1054
|
+
};
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
function nonEmpty(crumbs) {
|
|
1058
|
+
return crumbs.length > 0 ? crumbs : void 0;
|
|
1059
|
+
}
|
|
1060
|
+
function recordActionOf(match) {
|
|
1061
|
+
if (match === null || match.kind !== "resource") return void 0;
|
|
1062
|
+
switch (match.action) {
|
|
1063
|
+
case "show":
|
|
1064
|
+
case "edit":
|
|
1065
|
+
case "create": return match.action;
|
|
1066
|
+
default: return;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
function recordTrail(ctx, input) {
|
|
1070
|
+
const { resource, action, id } = input;
|
|
1071
|
+
const leadingSegments = nonEmpty(groupCrumbs(ctx, input.groupChain));
|
|
1072
|
+
if (action !== "create" && id === "") return {
|
|
1073
|
+
resource,
|
|
1074
|
+
leadingSegments
|
|
1075
|
+
};
|
|
1076
|
+
const recordLabel = input.title || id;
|
|
1077
|
+
const middleSegments = action === "edit" ? [require_place_node.isShowAvailable(ctx.registry, resource) ? {
|
|
1078
|
+
label: recordLabel,
|
|
1079
|
+
href: (0, _burojs_react.resolveRoute)(require_place_node.showTarget(resource, id), ctx.registry)
|
|
1080
|
+
} : { label: recordLabel }] : void 0;
|
|
1081
|
+
const pageLabel = action === "create" ? ctx.t("breadcrumbs.create") : action === "edit" ? ctx.t("breadcrumbs.edit") : recordLabel;
|
|
1082
|
+
return {
|
|
1083
|
+
resource,
|
|
1084
|
+
recordId: action === "create" ? void 0 : id,
|
|
1085
|
+
pageLabel,
|
|
1086
|
+
leadingSegments,
|
|
1087
|
+
middleSegments
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function recordTrailRoute(registry, match) {
|
|
1091
|
+
if (match === null || match.kind !== "resource") return null;
|
|
1092
|
+
const resource = canonicalNavKey(match);
|
|
1093
|
+
const { groupChain } = match;
|
|
1094
|
+
if (match.action === "flow") return {
|
|
1095
|
+
resource,
|
|
1096
|
+
action: "show",
|
|
1097
|
+
id: match.id ?? "",
|
|
1098
|
+
flow: match.flow ?? "",
|
|
1099
|
+
groupChain
|
|
1100
|
+
};
|
|
1101
|
+
const action = recordActionOf(match);
|
|
1102
|
+
if (action === void 0 || !require_place_node.recordRouteAvailable(registry, resource, action)) return null;
|
|
1103
|
+
return {
|
|
1104
|
+
resource,
|
|
1105
|
+
action,
|
|
1106
|
+
id: action === "create" ? "" : match.id ?? "",
|
|
1107
|
+
groupChain
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
function headerTrail(ctx, match) {
|
|
1111
|
+
if (match === null) return null;
|
|
1112
|
+
const key = canonicalNavKey(match);
|
|
1113
|
+
switch (match.kind) {
|
|
1114
|
+
case "resource": {
|
|
1115
|
+
const leadingSegments = nonEmpty(groupCrumbs(ctx, match.groupChain));
|
|
1116
|
+
const action = recordActionOf(match);
|
|
1117
|
+
if (recordTrailRoute(ctx.registry, match) !== null) return null;
|
|
1118
|
+
if (action === void 0) return {
|
|
1119
|
+
resource: key,
|
|
1120
|
+
leadingSegments
|
|
1121
|
+
};
|
|
1122
|
+
return {
|
|
1123
|
+
resource: key,
|
|
1124
|
+
recordId: match.id,
|
|
1125
|
+
pageLabel: ctx.t("router.notFoundTitle"),
|
|
1126
|
+
leadingSegments
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
case "custom-page": {
|
|
1130
|
+
const page = ctx.registry.customPages.get(match.name);
|
|
1131
|
+
if (page === void 0 || !ctx.isVisible(page)) return null;
|
|
1132
|
+
return {
|
|
1133
|
+
name: key,
|
|
1134
|
+
pageLabel: (0, _burojs_core.resolveNavLabel)(ctx.caps, `pages.${key}`, page?.label ?? match.name),
|
|
1135
|
+
leadingSegments: nonEmpty(groupCrumbs(ctx, match.groupChain))
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
case "group-index": {
|
|
1139
|
+
const own = match.groupChain[match.groupChain.length - 1];
|
|
1140
|
+
if (own === void 0) return null;
|
|
1141
|
+
const crumbs = groupCrumbs(ctx, match.groupChain);
|
|
1142
|
+
const current = crumbs[crumbs.length - 1];
|
|
1143
|
+
return {
|
|
1144
|
+
name: canonicalNavKey(own),
|
|
1145
|
+
pageLabel: current.label,
|
|
1146
|
+
leadingSegments: nonEmpty(crumbs.slice(0, -1))
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
default: return null;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function recentStorageKey(userId) {
|
|
1153
|
+
return `buro:recent:v2:${userId}`;
|
|
1154
|
+
}
|
|
1155
|
+
function isRecent(value) {
|
|
1156
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1157
|
+
const entry = value;
|
|
1158
|
+
return typeof entry.canonicalRef === "string" && typeof entry.id === "string" && typeof entry.label === "string" && (entry.labelField === void 0 || typeof entry.labelField === "string") && typeof entry.at === "string";
|
|
1159
|
+
}
|
|
1160
|
+
function parse(raw) {
|
|
1161
|
+
if (raw === null) return [];
|
|
1162
|
+
try {
|
|
1163
|
+
const parsed = JSON.parse(raw);
|
|
1164
|
+
if (typeof parsed !== "object" || parsed === null) return [];
|
|
1165
|
+
const envelope = parsed;
|
|
1166
|
+
if (envelope.version !== 2 || !Array.isArray(envelope.items)) return [];
|
|
1167
|
+
return envelope.items.filter(isRecent);
|
|
1168
|
+
} catch {
|
|
1169
|
+
return [];
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
function readRecentRecords(userId) {
|
|
1173
|
+
if (typeof window === "undefined") return [];
|
|
1174
|
+
try {
|
|
1175
|
+
return parse(window.localStorage.getItem(recentStorageKey(userId)));
|
|
1176
|
+
} catch {
|
|
1177
|
+
return [];
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
function writeRecentRecord(userId, entry) {
|
|
1181
|
+
if (typeof window === "undefined") return;
|
|
1182
|
+
try {
|
|
1183
|
+
const key = recentStorageKey(userId);
|
|
1184
|
+
const envelope = {
|
|
1185
|
+
version: 2,
|
|
1186
|
+
items: [entry, ...parse(window.localStorage.getItem(key)).filter((item) => !(item.canonicalRef === entry.canonicalRef && item.id === entry.id))].slice(0, 5)
|
|
1187
|
+
};
|
|
1188
|
+
window.localStorage.setItem(key, JSON.stringify(envelope));
|
|
1189
|
+
} catch {}
|
|
1190
|
+
}
|
|
1191
|
+
function text(value) {
|
|
1192
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1193
|
+
}
|
|
1194
|
+
const RECENT_LABEL_KEYS = [
|
|
1195
|
+
"alias",
|
|
1196
|
+
"name",
|
|
1197
|
+
"title",
|
|
1198
|
+
"label"
|
|
1199
|
+
];
|
|
1200
|
+
function recentLabel(record, id, isGuarded = () => false) {
|
|
1201
|
+
for (const key of RECENT_LABEL_KEYS) {
|
|
1202
|
+
if (isGuarded(key)) continue;
|
|
1203
|
+
const value = text(record[key]);
|
|
1204
|
+
if (value !== void 0) return {
|
|
1205
|
+
label: value,
|
|
1206
|
+
field: key
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
return { label: id };
|
|
1210
|
+
}
|
|
1211
|
+
//#endregion
|
|
1212
|
+
//#region src/layout/use-record-visit.ts
|
|
1213
|
+
function useRecordVisit(resource, record, opts) {
|
|
1214
|
+
const userId = (0, _burojs_react.useCurrentUserId)();
|
|
1215
|
+
const raw = record?.id;
|
|
1216
|
+
const id = typeof raw === "string" || typeof raw === "number" ? String(raw) : void 0;
|
|
1217
|
+
const app = (0, _burojs_react.useApp)();
|
|
1218
|
+
const fields = app.registry.get(resource)?.fields;
|
|
1219
|
+
const rules = app.ruleSource?.rules(resource) ?? [];
|
|
1220
|
+
const isGuarded = (field) => fields !== void 0 && Object.hasOwn(fields, field) && (0, _burojs_core.fieldSensitivity)(fields[field]) !== void 0 || (0, _burojs_core.addressesCapability)(rules, "view", {
|
|
1221
|
+
kind: "field",
|
|
1222
|
+
name: field
|
|
1223
|
+
});
|
|
1224
|
+
const picked = record != null && id !== void 0 ? recentLabel(record, id, isGuarded) : void 0;
|
|
1225
|
+
const label = picked?.label;
|
|
1226
|
+
const labelField = picked?.field;
|
|
1227
|
+
const canonicalRef = require_check_confirm_dialog.canonicalResourceName(resource);
|
|
1228
|
+
const { isCurrentData } = opts;
|
|
1229
|
+
(0, react.useEffect)(() => {
|
|
1230
|
+
if (userId === null || !isCurrentData || id === void 0 || id === "") return;
|
|
1231
|
+
if (label === void 0) return;
|
|
1232
|
+
writeRecentRecord(userId, {
|
|
1233
|
+
canonicalRef,
|
|
1234
|
+
id,
|
|
1235
|
+
label,
|
|
1236
|
+
...labelField === void 0 ? {} : { labelField },
|
|
1237
|
+
at: new Date().toISOString()
|
|
1238
|
+
});
|
|
1239
|
+
}, [
|
|
1240
|
+
userId,
|
|
1241
|
+
isCurrentData,
|
|
1242
|
+
canonicalRef,
|
|
1243
|
+
id,
|
|
1244
|
+
label,
|
|
1245
|
+
labelField
|
|
1246
|
+
]);
|
|
1247
|
+
}
|
|
1248
|
+
//#endregion
|
|
1249
|
+
//#region src/layout/record-page.tsx
|
|
1250
|
+
function recordErrorState(error) {
|
|
1251
|
+
const { kind } = (0, _burojs_core.normalizeDataError)(error);
|
|
1252
|
+
if (kind === "not-found" || kind === "forbidden") return "not-found";
|
|
1253
|
+
if (kind === "auth") return "session-expired";
|
|
1254
|
+
return "error";
|
|
1255
|
+
}
|
|
1256
|
+
function useMountedRef() {
|
|
1257
|
+
const mounted = (0, react.useRef)(true);
|
|
1258
|
+
(0, react.useEffect)(() => {
|
|
1259
|
+
mounted.current = true;
|
|
1260
|
+
return () => {
|
|
1261
|
+
mounted.current = false;
|
|
1262
|
+
};
|
|
1263
|
+
}, []);
|
|
1264
|
+
return mounted;
|
|
1265
|
+
}
|
|
1266
|
+
function RecordRouteHeader({ resource, action, id, record }) {
|
|
1267
|
+
const recordName = (0, _burojs_react.useRecordTitle)(resource, action === "create" ? void 0 : id, record);
|
|
1268
|
+
usePublishRecordTrail(action === "create" ? null : {
|
|
1269
|
+
key: recordTrailKey(resource, id, action),
|
|
1270
|
+
title: recordName
|
|
1271
|
+
});
|
|
1272
|
+
return (0, react_jsx_runtime.jsx)(require_place_node.PageHeader, {
|
|
1273
|
+
resource,
|
|
1274
|
+
mode: action,
|
|
1275
|
+
...action === "create" ? {} : { recordName }
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
function RecordShow({ resource, id, record, shown }) {
|
|
1279
|
+
return (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)(RecordRouteHeader, {
|
|
1280
|
+
resource,
|
|
1281
|
+
action: "show",
|
|
1282
|
+
id,
|
|
1283
|
+
record: shown
|
|
1284
|
+
}), (0, react_jsx_runtime.jsx)(RecordScreen, {
|
|
1285
|
+
resource,
|
|
1286
|
+
mode: "show",
|
|
1287
|
+
recordId: id,
|
|
1288
|
+
record
|
|
1289
|
+
})] });
|
|
1290
|
+
}
|
|
1291
|
+
function RecordEdit({ resource, id, record, shown }) {
|
|
1292
|
+
const app = (0, _burojs_react.useApp)();
|
|
1293
|
+
const navigate = (0, _burojs_react.useRouteNavigate)();
|
|
1294
|
+
const mounted = useMountedRef();
|
|
1295
|
+
const back = require_place_node.recordReturnTarget(app.registry, resource, id);
|
|
1296
|
+
return (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)(RecordRouteHeader, {
|
|
1297
|
+
resource,
|
|
1298
|
+
action: "edit",
|
|
1299
|
+
id,
|
|
1300
|
+
record: shown
|
|
1301
|
+
}), (0, react_jsx_runtime.jsx)(RecordScreen, {
|
|
1302
|
+
resource,
|
|
1303
|
+
mode: "edit",
|
|
1304
|
+
recordId: id,
|
|
1305
|
+
record,
|
|
1306
|
+
onCancel: () => navigate(back),
|
|
1307
|
+
onSuccess: () => {
|
|
1308
|
+
if (mounted.current) navigate(back);
|
|
1309
|
+
}
|
|
1310
|
+
})] });
|
|
1311
|
+
}
|
|
1312
|
+
function createdId(result) {
|
|
1313
|
+
const id = result?.id;
|
|
1314
|
+
return typeof id === "string" || typeof id === "number" ? String(id) : void 0;
|
|
1315
|
+
}
|
|
1316
|
+
function RecordCreate({ resource, defaults }) {
|
|
1317
|
+
const app = (0, _burojs_react.useApp)();
|
|
1318
|
+
const navigate = (0, _burojs_react.useRouteNavigate)();
|
|
1319
|
+
const mounted = useMountedRef();
|
|
1320
|
+
const adapter = (0, _burojs_react.useOptionalRouterAdapter)();
|
|
1321
|
+
const list = require_place_node.listTarget(resource);
|
|
1322
|
+
const type = adapter?.searchParams.get("type") ?? void 0;
|
|
1323
|
+
return (0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)(RecordRouteHeader, {
|
|
1324
|
+
resource,
|
|
1325
|
+
action: "create",
|
|
1326
|
+
id: ""
|
|
1327
|
+
}), (0, react_jsx_runtime.jsx)(require_place_node.RouteScreenClaimContext.Provider, {
|
|
1328
|
+
value: true,
|
|
1329
|
+
children: (0, react_jsx_runtime.jsx)(RecordScreen, {
|
|
1330
|
+
resource,
|
|
1331
|
+
mode: "create",
|
|
1332
|
+
...type === void 0 ? {} : { type },
|
|
1333
|
+
...defaults === void 0 ? {} : { defaults },
|
|
1334
|
+
onCancel: () => navigate(list),
|
|
1335
|
+
onSuccess: (result) => {
|
|
1336
|
+
if (!mounted.current) return;
|
|
1337
|
+
const id = createdId(result);
|
|
1338
|
+
navigate(id === void 0 ? list : require_place_node.recordReturnTarget(app.registry, resource, id));
|
|
1339
|
+
}
|
|
1340
|
+
})
|
|
1341
|
+
})] });
|
|
1342
|
+
}
|
|
1343
|
+
function RecordRead({ resource, action, id }) {
|
|
1344
|
+
const app = (0, _burojs_react.useApp)();
|
|
1345
|
+
const def = app.registry.get(resource);
|
|
1346
|
+
const projection = (0, react.useMemo)(() => def === void 0 ? void 0 : (0, _burojs_core.dtoFields)(def, "record"), [def]);
|
|
1347
|
+
const query = (0, _burojs_react.useOne)(resource, {
|
|
1348
|
+
id,
|
|
1349
|
+
retry: _burojs_react.retryRecordRead,
|
|
1350
|
+
...projection === void 0 ? {} : { projection }
|
|
1351
|
+
});
|
|
1352
|
+
const result = query.data;
|
|
1353
|
+
const read = result?.data;
|
|
1354
|
+
const projected = (0, _burojs_react.useProjectedRecord)(resource, read, { placeholder: query.isPlaceholderData });
|
|
1355
|
+
const showAvailability = (0, _burojs_react.useAvailabilityResolver)(resource, "show");
|
|
1356
|
+
const permissions = (0, _burojs_react.useCtx)((c) => c.permissions);
|
|
1357
|
+
const showRuled = (0, _burojs_core.addressesCapability)(app.ruleSource.rules(resource), "run", {
|
|
1358
|
+
kind: "action",
|
|
1359
|
+
name: "show"
|
|
1360
|
+
});
|
|
1361
|
+
const showHidden = showRuled && read !== null && typeof read === "object" && showAvailability("show", read).state !== "available";
|
|
1362
|
+
const showSettled = !showRuled || !query.isPlaceholderData && permissions?.status !== "loading" && permissions?.status !== "error";
|
|
1363
|
+
useRecordVisit(resource, projected.status === "ready" && showSettled && !showHidden ? projected.value : null, { isCurrentData: !query.isPlaceholderData });
|
|
1364
|
+
const route = (0, react.useMemo)(() => Object.freeze({ id }), [id]);
|
|
1365
|
+
const availability = (0, _burojs_react.useAvailabilityResolver)(resource, "edit");
|
|
1366
|
+
if (result !== void 0) {
|
|
1367
|
+
const record = result.data;
|
|
1368
|
+
if (record === null || typeof record !== "object") return (0, react_jsx_runtime.jsx)(RecordState, { state: "not-found" });
|
|
1369
|
+
const loaded = record;
|
|
1370
|
+
if (showRuled) {
|
|
1371
|
+
if (query.isPlaceholderData || permissions?.status === "loading") return (0, react_jsx_runtime.jsx)(RecordState, { state: "loading" });
|
|
1372
|
+
if (permissions?.status === "error") return (0, react_jsx_runtime.jsx)(RecordState, { state: "not-found" });
|
|
1373
|
+
if (showHidden) return (0, react_jsx_runtime.jsx)(RecordState, { state: "not-found" });
|
|
1374
|
+
}
|
|
1375
|
+
if (projected.status === "pending" || projected.value === null) return (0, react_jsx_runtime.jsx)(RecordState, { state: "loading" });
|
|
1376
|
+
const shown = projected.value;
|
|
1377
|
+
if (action === "edit") {
|
|
1378
|
+
const edit = availability("edit", loaded).state;
|
|
1379
|
+
if (edit === "hidden" || edit === "absent") return (0, react_jsx_runtime.jsx)(RecordState, { state: "not-found" });
|
|
1380
|
+
}
|
|
1381
|
+
return (0, react_jsx_runtime.jsx)(_burojs_react.RouteSourceContext.Provider, {
|
|
1382
|
+
value: route,
|
|
1383
|
+
children: (0, react_jsx_runtime.jsx)(require_place_node.RouteScreenClaimContext.Provider, {
|
|
1384
|
+
value: true,
|
|
1385
|
+
children: action === "edit" ? (0, react_jsx_runtime.jsx)(RecordEdit, {
|
|
1386
|
+
resource,
|
|
1387
|
+
id,
|
|
1388
|
+
record: loaded,
|
|
1389
|
+
shown
|
|
1390
|
+
}) : (0, react_jsx_runtime.jsx)(RecordShow, {
|
|
1391
|
+
resource,
|
|
1392
|
+
id,
|
|
1393
|
+
record: loaded,
|
|
1394
|
+
shown
|
|
1395
|
+
})
|
|
1396
|
+
})
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
if (query.error != null) {
|
|
1400
|
+
const state = recordErrorState(query.error);
|
|
1401
|
+
return (0, react_jsx_runtime.jsx)(RecordState, {
|
|
1402
|
+
state,
|
|
1403
|
+
onRetry: state === "error" ? () => void query.refetch() : void 0
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
return (0, react_jsx_runtime.jsx)(RecordState, { state: "loading" });
|
|
1407
|
+
}
|
|
1408
|
+
function RecordRouteBody(props) {
|
|
1409
|
+
if (props.action === "create") return (0, react_jsx_runtime.jsx)(RecordCreate, { ...props });
|
|
1410
|
+
if (props.id === "") return (0, react_jsx_runtime.jsx)(RecordState, { state: "not-found" });
|
|
1411
|
+
return (0, react_jsx_runtime.jsx)(RecordRead, { ...props });
|
|
1412
|
+
}
|
|
1413
|
+
function RecordRoute(props) {
|
|
1414
|
+
const missingRuntime = (0, _burojs_react.useOptionalDataRuntime)() === null;
|
|
1415
|
+
(0, react.useEffect)(() => {
|
|
1416
|
+
if (!missingRuntime || process.env.NODE_ENV === "production") return;
|
|
1417
|
+
console.error(`[burojs] The '${props.resource}' record page has no data runtime to read or write with: BuroProvider.dataRuntime is not set. Pass <BuroProvider dataRuntime={…}> (createDataRuntime from @burojs/react); until then record routes render the error state.`);
|
|
1418
|
+
}, [missingRuntime, props.resource]);
|
|
1419
|
+
if (missingRuntime) return (0, react_jsx_runtime.jsx)(RecordState, { state: "error" });
|
|
1420
|
+
return (0, react_jsx_runtime.jsx)(RecordRouteBody, { ...props }, `${props.resource}:${props.id}`);
|
|
1421
|
+
}
|
|
1422
|
+
function recordPageProblem(input) {
|
|
1423
|
+
const { props, resource, action } = input;
|
|
1424
|
+
if (resource === void 0 || action === void 0) return "[burojs] <RecordPage> needs both `resource` and `action`. Pass them; until then it renders not-found.";
|
|
1425
|
+
if (!input.onRecordRoute) return `[burojs] <RecordPage resource="${resource}" action="${action}"> is not on a record route (show, edit or create), so it has no record to read: render it on one, under a RouterAdapterProvider. Until then it renders not-found.`;
|
|
1426
|
+
if (props.defaults !== void 0 && action !== "create") return `[burojs] <RecordPage defaults> applies to create only; on this '${action}' route it is ignored.`;
|
|
1427
|
+
return null;
|
|
1428
|
+
}
|
|
1429
|
+
const recordPageProblemsLogged = new WeakMap();
|
|
1430
|
+
function RecordPage(props) {
|
|
1431
|
+
const app = (0, _burojs_react.useApp)();
|
|
1432
|
+
const match = useMatch();
|
|
1433
|
+
const id = match === null ? null : match.id ?? "";
|
|
1434
|
+
const { resource, action } = props;
|
|
1435
|
+
const onRecordRoute = recordActionOf(match) !== void 0;
|
|
1436
|
+
const problem = recordPageProblem({
|
|
1437
|
+
props,
|
|
1438
|
+
resource,
|
|
1439
|
+
action,
|
|
1440
|
+
onRecordRoute
|
|
1441
|
+
});
|
|
1442
|
+
(0, react.useEffect)(() => {
|
|
1443
|
+
if (problem === null || process.env.NODE_ENV === "production") return;
|
|
1444
|
+
let logged = recordPageProblemsLogged.get(app);
|
|
1445
|
+
if (logged === void 0) {
|
|
1446
|
+
logged = new Set();
|
|
1447
|
+
recordPageProblemsLogged.set(app, logged);
|
|
1448
|
+
}
|
|
1449
|
+
if (logged.has(problem)) return;
|
|
1450
|
+
logged.add(problem);
|
|
1451
|
+
console.error(problem);
|
|
1452
|
+
}, [app, problem]);
|
|
1453
|
+
if (resource === void 0 || action === void 0 || id === null || !onRecordRoute) return (0, react_jsx_runtime.jsx)(RecordState, { state: "not-found" });
|
|
1454
|
+
const route = action === "create" ? {
|
|
1455
|
+
resource,
|
|
1456
|
+
action,
|
|
1457
|
+
defaults: props.defaults
|
|
1458
|
+
} : {
|
|
1459
|
+
resource,
|
|
1460
|
+
action
|
|
1461
|
+
};
|
|
1462
|
+
return (0, react_jsx_runtime.jsx)(RecordRoute, {
|
|
1463
|
+
...route,
|
|
1464
|
+
id
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
//#endregion
|
|
1468
|
+
//#region src/layout/resource-page-router.tsx
|
|
1469
|
+
function ResourcePageRouterEmpty() {
|
|
1470
|
+
const t = (0, _burojs_react.useT)();
|
|
1471
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1472
|
+
"data-testid": "resource-page-router-empty",
|
|
1473
|
+
className: "flex flex-col items-center justify-center gap-1 px-6 py-24 text-center",
|
|
1474
|
+
children: [(0, react_jsx_runtime.jsx)("h2", {
|
|
1475
|
+
className: "text-base font-semibold tracking-tight",
|
|
1476
|
+
children: t("router.emptyTitle")
|
|
1477
|
+
}), (0, react_jsx_runtime.jsx)("p", {
|
|
1478
|
+
className: "max-w-sm text-sm text-muted-foreground",
|
|
1479
|
+
children: t("router.emptyBody")
|
|
1480
|
+
})]
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
function ResourcePageRouter({ match }) {
|
|
1484
|
+
const app = (0, _burojs_react.useApp)();
|
|
1485
|
+
const qualifiedMatch = match;
|
|
1486
|
+
const resource = canonicalNavKey(qualifiedMatch);
|
|
1487
|
+
const target = canonicalNavKey({
|
|
1488
|
+
name: match.target ?? match.name,
|
|
1489
|
+
ref: qualifiedMatch.targetRef
|
|
1490
|
+
});
|
|
1491
|
+
const recordAction = recordActionOf(match);
|
|
1492
|
+
switch (match.kind) {
|
|
1493
|
+
case "resource":
|
|
1494
|
+
if (match.action === "list") {
|
|
1495
|
+
const def = app.registry.get(resource);
|
|
1496
|
+
return def !== void 0 && !(0, _burojs_core.hasListPage)(def) ? (0, react_jsx_runtime.jsx)(NavAreaNotFound, {}) : (0, react_jsx_runtime.jsx)(ResourceListPage, { resource });
|
|
1497
|
+
}
|
|
1498
|
+
if (match.action === "flow") return (0, react_jsx_runtime.jsx)(PageFlowRouteLoader, { match });
|
|
1499
|
+
if (recordAction === void 0) return (0, react_jsx_runtime.jsx)(ResourcePageRouterEmpty, {});
|
|
1500
|
+
if (!require_place_node.recordRouteAvailable(app.registry, resource, recordAction)) return (0, react_jsx_runtime.jsx)(NavAreaNotFound, {});
|
|
1501
|
+
return (0, react_jsx_runtime.jsx)(RecordRoute, {
|
|
1502
|
+
resource,
|
|
1503
|
+
action: recordAction,
|
|
1504
|
+
id: match.id ?? ""
|
|
1505
|
+
});
|
|
1506
|
+
case "custom-page": {
|
|
1507
|
+
const page = app.registry.customPages.get(resource);
|
|
1508
|
+
if (page === void 0) return (0, react_jsx_runtime.jsx)(ResourcePageRouterEmpty, {});
|
|
1509
|
+
if (!page.body?.length) return (0, react_jsx_runtime.jsx)(NavAreaNotFound, {});
|
|
1510
|
+
return (0, react_jsx_runtime.jsx)(PageHost, {
|
|
1511
|
+
page,
|
|
1512
|
+
match,
|
|
1513
|
+
notFound: (0, react_jsx_runtime.jsx)(NavAreaNotFound, {})
|
|
1514
|
+
}, resource);
|
|
1515
|
+
}
|
|
1516
|
+
case "group-index": {
|
|
1517
|
+
const indexPage = app.registry.customPages.get(target);
|
|
1518
|
+
if (indexPage?.body?.length) return (0, react_jsx_runtime.jsx)(PageHost, {
|
|
1519
|
+
page: indexPage,
|
|
1520
|
+
match,
|
|
1521
|
+
notFound: (0, react_jsx_runtime.jsx)(NavAreaNotFound, {})
|
|
1522
|
+
}, target);
|
|
1523
|
+
const targetDef = app.registry.get(target);
|
|
1524
|
+
if (targetDef !== void 0) return (0, _burojs_core.hasListPage)(targetDef) ? (0, react_jsx_runtime.jsx)(ResourceListPage, { resource: target }) : (0, react_jsx_runtime.jsx)(NavAreaNotFound, {});
|
|
1525
|
+
return (0, react_jsx_runtime.jsx)(ResourcePageRouterEmpty, {});
|
|
1526
|
+
}
|
|
1527
|
+
default: return (0, react_jsx_runtime.jsx)(ResourcePageRouterEmpty, {});
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
function NavAreaPageSkeleton() {
|
|
1531
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1532
|
+
className: "space-y-3 p-6",
|
|
1533
|
+
role: "status",
|
|
1534
|
+
"aria-busy": "true",
|
|
1535
|
+
"aria-live": "polite",
|
|
1536
|
+
"data-testid": "nav-area-skeleton",
|
|
1537
|
+
children: [
|
|
1538
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1539
|
+
className: "sr-only",
|
|
1540
|
+
children: "Loading…"
|
|
1541
|
+
}),
|
|
1542
|
+
(0, react_jsx_runtime.jsx)("div", { className: "h-6 w-48 animate-pulse rounded bg-muted/60" }),
|
|
1543
|
+
(0, react_jsx_runtime.jsx)("div", { className: "h-4 w-full animate-pulse rounded bg-muted/40" }),
|
|
1544
|
+
(0, react_jsx_runtime.jsx)("div", { className: "h-4 w-5/6 animate-pulse rounded bg-muted/40" }),
|
|
1545
|
+
(0, react_jsx_runtime.jsx)("div", { className: "h-4 w-2/3 animate-pulse rounded bg-muted/40" })
|
|
1546
|
+
]
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
function NavAreaNotFound() {
|
|
1550
|
+
const t = (0, _burojs_react.useT)();
|
|
1551
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1552
|
+
"data-testid": "nav-area-not-found",
|
|
1553
|
+
className: "flex flex-col items-center justify-center gap-1 px-6 py-24 text-center",
|
|
1554
|
+
children: [(0, react_jsx_runtime.jsx)("h2", {
|
|
1555
|
+
className: "text-base font-semibold tracking-tight",
|
|
1556
|
+
children: t("router.notFoundTitle")
|
|
1557
|
+
}), (0, react_jsx_runtime.jsx)("p", {
|
|
1558
|
+
className: "max-w-sm text-sm text-muted-foreground",
|
|
1559
|
+
children: t("router.notFoundBody")
|
|
1560
|
+
})]
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
const adapterNoticeGiven = new WeakSet();
|
|
1564
|
+
function NavAreaPage() {
|
|
1565
|
+
const match = useMatch();
|
|
1566
|
+
const app = (0, _burojs_react.useApp)();
|
|
1567
|
+
const noAdapter = (0, _burojs_react.useOptionalRouterAdapter)() === null;
|
|
1568
|
+
(0, react.useEffect)(() => {
|
|
1569
|
+
if (!noAdapter || process.env.NODE_ENV === "production") return;
|
|
1570
|
+
if (adapterNoticeGiven.has(app)) return;
|
|
1571
|
+
adapterNoticeGiven.add(app);
|
|
1572
|
+
console.error("[burojs] <NavAreaPage> has no location to match: no RouterAdapterProvider is mounted. Mount your router’s adapter (RouterAdapterProvider from @burojs/react) above <BuroProvider>; until then every route renders not-found.");
|
|
1573
|
+
}, [app, noAdapter]);
|
|
1574
|
+
return (0, react_jsx_runtime.jsx)(react.Suspense, {
|
|
1575
|
+
fallback: (0, react_jsx_runtime.jsx)(NavAreaPageSkeleton, {}),
|
|
1576
|
+
children: match ? (0, react_jsx_runtime.jsx)(NavChrome, { children: (0, react_jsx_runtime.jsx)(ResourcePageRouter, { match }) }) : (0, react_jsx_runtime.jsx)(NavAreaNotFound, {})
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
//#endregion
|
|
1580
|
+
Object.defineProperty(exports, "NavAreaNotFound", {
|
|
1581
|
+
enumerable: true,
|
|
1582
|
+
get: function() {
|
|
1583
|
+
return NavAreaNotFound;
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
Object.defineProperty(exports, "NavAreaPage", {
|
|
1587
|
+
enumerable: true,
|
|
1588
|
+
get: function() {
|
|
1589
|
+
return NavAreaPage;
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
Object.defineProperty(exports, "NavChrome", {
|
|
1593
|
+
enumerable: true,
|
|
1594
|
+
get: function() {
|
|
1595
|
+
return NavChrome;
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
Object.defineProperty(exports, "NavChromeBoundaryProvider", {
|
|
1599
|
+
enumerable: true,
|
|
1600
|
+
get: function() {
|
|
1601
|
+
return NavChromeBoundaryProvider;
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
Object.defineProperty(exports, "PlannedBody", {
|
|
1605
|
+
enumerable: true,
|
|
1606
|
+
get: function() {
|
|
1607
|
+
return PlannedBody;
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
Object.defineProperty(exports, "RECENT_LABEL_KEYS", {
|
|
1611
|
+
enumerable: true,
|
|
1612
|
+
get: function() {
|
|
1613
|
+
return RECENT_LABEL_KEYS;
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
Object.defineProperty(exports, "RecordPage", {
|
|
1617
|
+
enumerable: true,
|
|
1618
|
+
get: function() {
|
|
1619
|
+
return RecordPage;
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
Object.defineProperty(exports, "RecordScreen", {
|
|
1623
|
+
enumerable: true,
|
|
1624
|
+
get: function() {
|
|
1625
|
+
return RecordScreen;
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
Object.defineProperty(exports, "RecordState", {
|
|
1629
|
+
enumerable: true,
|
|
1630
|
+
get: function() {
|
|
1631
|
+
return RecordState;
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
Object.defineProperty(exports, "ResourceListPage", {
|
|
1635
|
+
enumerable: true,
|
|
1636
|
+
get: function() {
|
|
1637
|
+
return ResourceListPage;
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
Object.defineProperty(exports, "ResourcePageRouter", {
|
|
1641
|
+
enumerable: true,
|
|
1642
|
+
get: function() {
|
|
1643
|
+
return ResourcePageRouter;
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
Object.defineProperty(exports, "buildNavItems", {
|
|
1647
|
+
enumerable: true,
|
|
1648
|
+
get: function() {
|
|
1649
|
+
return buildNavItems;
|
|
1650
|
+
}
|
|
1651
|
+
});
|
|
1652
|
+
Object.defineProperty(exports, "canonicalNavKey", {
|
|
1653
|
+
enumerable: true,
|
|
1654
|
+
get: function() {
|
|
1655
|
+
return canonicalNavKey;
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
Object.defineProperty(exports, "canonicalResourceKey", {
|
|
1659
|
+
enumerable: true,
|
|
1660
|
+
get: function() {
|
|
1661
|
+
return canonicalResourceKey;
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
Object.defineProperty(exports, "headerTrail", {
|
|
1665
|
+
enumerable: true,
|
|
1666
|
+
get: function() {
|
|
1667
|
+
return headerTrail;
|
|
1668
|
+
}
|
|
1669
|
+
});
|
|
1670
|
+
Object.defineProperty(exports, "layoutSlot", {
|
|
1671
|
+
enumerable: true,
|
|
1672
|
+
get: function() {
|
|
1673
|
+
return layoutSlot;
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
Object.defineProperty(exports, "readRecentRecords", {
|
|
1677
|
+
enumerable: true,
|
|
1678
|
+
get: function() {
|
|
1679
|
+
return readRecentRecords;
|
|
1680
|
+
}
|
|
1681
|
+
});
|
|
1682
|
+
Object.defineProperty(exports, "recordErrorState", {
|
|
1683
|
+
enumerable: true,
|
|
1684
|
+
get: function() {
|
|
1685
|
+
return recordErrorState;
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
Object.defineProperty(exports, "recordTrail", {
|
|
1689
|
+
enumerable: true,
|
|
1690
|
+
get: function() {
|
|
1691
|
+
return recordTrail;
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
Object.defineProperty(exports, "recordTrailKey", {
|
|
1695
|
+
enumerable: true,
|
|
1696
|
+
get: function() {
|
|
1697
|
+
return recordTrailKey;
|
|
1698
|
+
}
|
|
1699
|
+
});
|
|
1700
|
+
Object.defineProperty(exports, "recordTrailRoute", {
|
|
1701
|
+
enumerable: true,
|
|
1702
|
+
get: function() {
|
|
1703
|
+
return recordTrailRoute;
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
Object.defineProperty(exports, "useFormControl", {
|
|
1707
|
+
enumerable: true,
|
|
1708
|
+
get: function() {
|
|
1709
|
+
return useFormControl;
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
Object.defineProperty(exports, "useHref", {
|
|
1713
|
+
enumerable: true,
|
|
1714
|
+
get: function() {
|
|
1715
|
+
return useHref;
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1718
|
+
Object.defineProperty(exports, "useLayout", {
|
|
1719
|
+
enumerable: true,
|
|
1720
|
+
get: function() {
|
|
1721
|
+
return useLayout;
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
Object.defineProperty(exports, "useLayoutResolver", {
|
|
1725
|
+
enumerable: true,
|
|
1726
|
+
get: function() {
|
|
1727
|
+
return useLayoutResolver;
|
|
1728
|
+
}
|
|
1729
|
+
});
|
|
1730
|
+
Object.defineProperty(exports, "useMatch", {
|
|
1731
|
+
enumerable: true,
|
|
1732
|
+
get: function() {
|
|
1733
|
+
return useMatch;
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
Object.defineProperty(exports, "useNavChromeBoundary", {
|
|
1737
|
+
enumerable: true,
|
|
1738
|
+
get: function() {
|
|
1739
|
+
return useNavChromeBoundary;
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
Object.defineProperty(exports, "useNavVisible", {
|
|
1743
|
+
enumerable: true,
|
|
1744
|
+
get: function() {
|
|
1745
|
+
return useNavVisible;
|
|
1746
|
+
}
|
|
1747
|
+
});
|
|
1748
|
+
Object.defineProperty(exports, "usePageAllowed", {
|
|
1749
|
+
enumerable: true,
|
|
1750
|
+
get: function() {
|
|
1751
|
+
return usePageAllowed;
|
|
1752
|
+
}
|
|
1753
|
+
});
|
|
1754
|
+
Object.defineProperty(exports, "usePublishRecordTrail", {
|
|
1755
|
+
enumerable: true,
|
|
1756
|
+
get: function() {
|
|
1757
|
+
return usePublishRecordTrail;
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
Object.defineProperty(exports, "usePublishedRecordTrail", {
|
|
1761
|
+
enumerable: true,
|
|
1762
|
+
get: function() {
|
|
1763
|
+
return usePublishedRecordTrail;
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
Object.defineProperty(exports, "useRecordVisit", {
|
|
1767
|
+
enumerable: true,
|
|
1768
|
+
get: function() {
|
|
1769
|
+
return useRecordVisit;
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1772
|
+
Object.defineProperty(exports, "useTrailContext", {
|
|
1773
|
+
enumerable: true,
|
|
1774
|
+
get: function() {
|
|
1775
|
+
return useTrailContext;
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
|
|
1779
|
+
//# sourceMappingURL=resource-page-router-OSQyRA5b.cjs.map
|