@burojs/ui-shadcn 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +9 -0
  2. package/dist/bulk-summary-2HLgz7Il.cjs +169 -0
  3. package/dist/bulk-summary-2HLgz7Il.cjs.map +1 -0
  4. package/dist/bulk-summary-D6A0ua21.js +169 -0
  5. package/dist/bulk-summary-D6A0ua21.js.map +1 -0
  6. package/dist/{candidates-relation-picker-Cy0rTxvI.cjs → candidates-relation-picker-C47mF260.cjs} +4 -4
  7. package/dist/{candidates-relation-picker-Cy0rTxvI.cjs.map → candidates-relation-picker-C47mF260.cjs.map} +1 -1
  8. package/dist/{candidates-relation-picker-B116Bmqi.js → candidates-relation-picker-CGXItMN0.js} +3 -3
  9. package/dist/{candidates-relation-picker-B116Bmqi.js.map → candidates-relation-picker-CGXItMN0.js.map} +1 -1
  10. package/dist/{check-confirm-dialog--gPFnEeg.cjs → check-confirm-dialog-BT04Mieo.cjs} +475 -107
  11. package/dist/check-confirm-dialog-BT04Mieo.cjs.map +1 -0
  12. package/dist/{check-confirm-dialog-bRT8lt0d.js → check-confirm-dialog-CtiRzOAt.js} +391 -108
  13. package/dist/check-confirm-dialog-CtiRzOAt.js.map +1 -0
  14. package/dist/{dialog-B_rt9S1c.js → dialog-C123sVQ8.js} +4 -4
  15. package/dist/dialog-C123sVQ8.js.map +1 -0
  16. package/dist/{dialog-DRNUq9EA.cjs → dialog-vmMdfUWs.cjs} +3 -3
  17. package/dist/dialog-vmMdfUWs.cjs.map +1 -0
  18. package/dist/{filter-builder-nXqtM_h4.cjs → filter-builder-ByGhxLf_.cjs} +1021 -154
  19. package/dist/filter-builder-ByGhxLf_.cjs.map +1 -0
  20. package/dist/{filter-builder-Bhn-dhZV.js → filter-builder-z3Axzf9Y.js} +929 -140
  21. package/dist/filter-builder-z3Axzf9Y.js.map +1 -0
  22. package/dist/{flow-runtime-D3avVH0I.cjs → flow-runtime-BVFjMU5-.cjs} +64 -29
  23. package/dist/flow-runtime-BVFjMU5-.cjs.map +1 -0
  24. package/dist/{flow-runtime-s6lkRPKF.js → flow-runtime-DrUWyTS0.js} +65 -30
  25. package/dist/flow-runtime-DrUWyTS0.js.map +1 -0
  26. package/dist/{flow-screen-CCvgDI9G.js → flow-screen-3fk6J33y.js} +130 -87
  27. package/dist/flow-screen-3fk6J33y.js.map +1 -0
  28. package/dist/{flow-screen-Ci_Sik4Y.cjs → flow-screen-6y5E_HT6.cjs} +130 -87
  29. package/dist/flow-screen-6y5E_HT6.cjs.map +1 -0
  30. package/dist/index.cjs +1186 -950
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +631 -322
  33. package/dist/index.d.ts +631 -322
  34. package/dist/index.js +1100 -870
  35. package/dist/index.js.map +1 -1
  36. package/dist/{inline-flow-host-DV1et3c6.cjs → inline-flow-host-C4KmZIYE.cjs} +5 -5
  37. package/dist/inline-flow-host-C4KmZIYE.cjs.map +1 -0
  38. package/dist/{inline-flow-host-B6FI0ctQ.js → inline-flow-host-YnnvWjdU.js} +5 -5
  39. package/dist/inline-flow-host-YnnvWjdU.js.map +1 -0
  40. package/dist/{candidates-gate-D33jXLHO.cjs → lazy-relation-picker-dialog-BAKBRn3V.cjs} +10 -10
  41. package/dist/lazy-relation-picker-dialog-BAKBRn3V.cjs.map +1 -0
  42. package/dist/{candidates-gate-Bb70LPAy.js → lazy-relation-picker-dialog-DN4bvQfg.js} +11 -11
  43. package/dist/lazy-relation-picker-dialog-DN4bvQfg.js.map +1 -0
  44. package/dist/{page-flow-route-i3ZfJ1sd.js → page-flow-route-CtEfqAvU.js} +35 -23
  45. package/dist/page-flow-route-CtEfqAvU.js.map +1 -0
  46. package/dist/{page-flow-route-hdnULWqd.cjs → page-flow-route-DEjAxHe3.cjs} +42 -30
  47. package/dist/page-flow-route-DEjAxHe3.cjs.map +1 -0
  48. package/dist/{place-node-NraNYKPx.js → place-node-BdxT-30h.js} +1980 -1041
  49. package/dist/place-node-BdxT-30h.js.map +1 -0
  50. package/dist/{place-node-C5SmIAYU.cjs → place-node-Bhl8B3fF.cjs} +2038 -1087
  51. package/dist/place-node-Bhl8B3fF.cjs.map +1 -0
  52. package/dist/{related-list-cards-CFq3meUn.cjs → related-list-cards-Bc_vfCE_.cjs} +9 -4
  53. package/dist/{related-list-cards-CFq3meUn.cjs.map → related-list-cards-Bc_vfCE_.cjs.map} +1 -1
  54. package/dist/{related-list-cards-BWZ9GfOG.js → related-list-cards-BvkD5OcC.js} +9 -4
  55. package/dist/{related-list-cards-BWZ9GfOG.js.map → related-list-cards-BvkD5OcC.js.map} +1 -1
  56. package/dist/related-list-timeline-DCrwN9oO.js +167 -0
  57. package/dist/related-list-timeline-DCrwN9oO.js.map +1 -0
  58. package/dist/related-list-timeline-k0DtnxTs.cjs +167 -0
  59. package/dist/related-list-timeline-k0DtnxTs.cjs.map +1 -0
  60. package/dist/{related-rows-ea43qhhy.cjs → related-rows-BW9Jr07T.cjs} +27 -16
  61. package/dist/related-rows-BW9Jr07T.cjs.map +1 -0
  62. package/dist/{related-rows-o0_t-Yce.js → related-rows-CPE-9FFr.js} +25 -14
  63. package/dist/related-rows-CPE-9FFr.js.map +1 -0
  64. package/dist/{search-access-CPPmfuvy.js → relation-create-C43cdPQt.js} +595 -13
  65. package/dist/relation-create-C43cdPQt.js.map +1 -0
  66. package/dist/{search-access-Dugvttlg.cjs → relation-create-DDEYcKfw.cjs} +633 -14
  67. package/dist/relation-create-DDEYcKfw.cjs.map +1 -0
  68. package/dist/{relation-many-picker-bvkjkv-k.js → relation-many-picker-BlrGRwMV.js} +210 -104
  69. package/dist/relation-many-picker-BlrGRwMV.js.map +1 -0
  70. package/dist/{relation-many-picker-C8zFEjwd.cjs → relation-many-picker-Br-zBHxT.cjs} +218 -112
  71. package/dist/relation-many-picker-Br-zBHxT.cjs.map +1 -0
  72. package/dist/{relation-picker-gag0qSR5.cjs → relation-picker-ADjxI_TI.cjs} +36 -73
  73. package/dist/relation-picker-ADjxI_TI.cjs.map +1 -0
  74. package/dist/{relation-picker-dialog-B6nIiJBf.js → relation-picker-dialog-CyKliBkN.js} +134 -36
  75. package/dist/relation-picker-dialog-CyKliBkN.js.map +1 -0
  76. package/dist/{relation-picker-dialog-0IzkjmiA.cjs → relation-picker-dialog-DimVQF9Y.cjs} +133 -35
  77. package/dist/relation-picker-dialog-DimVQF9Y.cjs.map +1 -0
  78. package/dist/{relation-picker-Z0ZJJ5uS.js → relation-picker-x7P5HKie.js} +30 -61
  79. package/dist/relation-picker-x7P5HKie.js.map +1 -0
  80. package/dist/relation-select-B8Fq4o-t.js +48 -0
  81. package/dist/relation-select-B8Fq4o-t.js.map +1 -0
  82. package/dist/relation-select-Cr3CfNLy.cjs +55 -0
  83. package/dist/relation-select-Cr3CfNLy.cjs.map +1 -0
  84. package/dist/{resource-page-router-3RUf9d1j.js → resource-page-router-BRpUnhay.js} +634 -264
  85. package/dist/resource-page-router-BRpUnhay.js.map +1 -0
  86. package/dist/{resource-page-router-PXfMW9nh.cjs → resource-page-router-qEd2xb55.cjs} +694 -312
  87. package/dist/resource-page-router-qEd2xb55.cjs.map +1 -0
  88. package/dist/{rich-text-vTyVjDoa.cjs → rich-text-C_7JCvRo.cjs} +2 -2
  89. package/dist/{rich-text-vTyVjDoa.cjs.map → rich-text-C_7JCvRo.cjs.map} +1 -1
  90. package/dist/{rich-text-DFerywgz.js → rich-text-RcPBf6hr.js} +2 -2
  91. package/dist/{rich-text-DFerywgz.js.map → rich-text-RcPBf6hr.js.map} +1 -1
  92. package/dist/success-notices-Dl3M1fZP.cjs +46 -0
  93. package/dist/success-notices-Dl3M1fZP.cjs.map +1 -0
  94. package/dist/success-notices-LDEgEkc4.js +41 -0
  95. package/dist/success-notices-LDEgEkc4.js.map +1 -0
  96. package/dist/{toaster-Cr51X7bz.cjs → toaster-BbByOytP.cjs} +60 -15
  97. package/dist/toaster-BbByOytP.cjs.map +1 -0
  98. package/dist/{toaster-C02XbB9r.js → toaster-Ct_YHGs6.js} +43 -16
  99. package/dist/toaster-Ct_YHGs6.js.map +1 -0
  100. package/dist/{use-dialog-filter-state-C--oMAZG.js → use-dialog-filter-state-B_MqzHaT.js} +5 -4
  101. package/dist/use-dialog-filter-state-B_MqzHaT.js.map +1 -0
  102. package/dist/{use-dialog-filter-state-Dhe8PcB5.cjs → use-dialog-filter-state-tNvqAAjh.cjs} +6 -5
  103. package/dist/use-dialog-filter-state-tNvqAAjh.cjs.map +1 -0
  104. package/dist/use-record-subtitle-bfwIoUme.cjs +70 -0
  105. package/dist/use-record-subtitle-bfwIoUme.cjs.map +1 -0
  106. package/dist/use-record-subtitle-gIaCGgbI.js +53 -0
  107. package/dist/use-record-subtitle-gIaCGgbI.js.map +1 -0
  108. package/package.json +11 -11
  109. package/dist/bulk-summary-CwSd6Yjb.cjs +0 -108
  110. package/dist/bulk-summary-CwSd6Yjb.cjs.map +0 -1
  111. package/dist/bulk-summary-GxMDP4bw.js +0 -108
  112. package/dist/bulk-summary-GxMDP4bw.js.map +0 -1
  113. package/dist/candidates-gate-Bb70LPAy.js.map +0 -1
  114. package/dist/candidates-gate-D33jXLHO.cjs.map +0 -1
  115. package/dist/check-confirm-dialog--gPFnEeg.cjs.map +0 -1
  116. package/dist/check-confirm-dialog-bRT8lt0d.js.map +0 -1
  117. package/dist/dialog-B_rt9S1c.js.map +0 -1
  118. package/dist/dialog-DRNUq9EA.cjs.map +0 -1
  119. package/dist/filter-builder-Bhn-dhZV.js.map +0 -1
  120. package/dist/filter-builder-nXqtM_h4.cjs.map +0 -1
  121. package/dist/flow-runtime-D3avVH0I.cjs.map +0 -1
  122. package/dist/flow-runtime-s6lkRPKF.js.map +0 -1
  123. package/dist/flow-screen-CCvgDI9G.js.map +0 -1
  124. package/dist/flow-screen-Ci_Sik4Y.cjs.map +0 -1
  125. package/dist/inline-flow-host-B6FI0ctQ.js.map +0 -1
  126. package/dist/inline-flow-host-DV1et3c6.cjs.map +0 -1
  127. package/dist/page-flow-route-hdnULWqd.cjs.map +0 -1
  128. package/dist/page-flow-route-i3ZfJ1sd.js.map +0 -1
  129. package/dist/place-node-C5SmIAYU.cjs.map +0 -1
  130. package/dist/place-node-NraNYKPx.js.map +0 -1
  131. package/dist/related-list-timeline-CCzl71aA.cjs +0 -142
  132. package/dist/related-list-timeline-CCzl71aA.cjs.map +0 -1
  133. package/dist/related-list-timeline-yrPsjA3F.js +0 -142
  134. package/dist/related-list-timeline-yrPsjA3F.js.map +0 -1
  135. package/dist/related-rows-ea43qhhy.cjs.map +0 -1
  136. package/dist/related-rows-o0_t-Yce.js.map +0 -1
  137. package/dist/relation-create-IA40t1hi.cjs +0 -364
  138. package/dist/relation-create-IA40t1hi.cjs.map +0 -1
  139. package/dist/relation-create-h6F9B4C4.js +0 -339
  140. package/dist/relation-create-h6F9B4C4.js.map +0 -1
  141. package/dist/relation-many-picker-C8zFEjwd.cjs.map +0 -1
  142. package/dist/relation-many-picker-bvkjkv-k.js.map +0 -1
  143. package/dist/relation-picker-Z0ZJJ5uS.js.map +0 -1
  144. package/dist/relation-picker-dialog-0IzkjmiA.cjs.map +0 -1
  145. package/dist/relation-picker-dialog-B6nIiJBf.js.map +0 -1
  146. package/dist/relation-picker-gag0qSR5.cjs.map +0 -1
  147. package/dist/resource-page-router-3RUf9d1j.js.map +0 -1
  148. package/dist/resource-page-router-PXfMW9nh.cjs.map +0 -1
  149. package/dist/search-access-CPPmfuvy.js.map +0 -1
  150. package/dist/search-access-Dugvttlg.cjs.map +0 -1
  151. package/dist/shell-BPlk8hEa.js +0 -205
  152. package/dist/shell-BPlk8hEa.js.map +0 -1
  153. package/dist/shell-DxhlsPwm.cjs +0 -271
  154. package/dist/shell-DxhlsPwm.cjs.map +0 -1
  155. package/dist/toaster-C02XbB9r.js.map +0 -1
  156. package/dist/toaster-Cr51X7bz.cjs.map +0 -1
  157. package/dist/use-dialog-filter-state-C--oMAZG.js.map +0 -1
  158. package/dist/use-dialog-filter-state-Dhe8PcB5.cjs.map +0 -1
@@ -1,10 +1,11 @@
1
1
  const require_use_theme = require("./use-theme-DFlpXils.cjs");
2
- const require_toaster = require("./toaster-Cr51X7bz.cjs");
2
+ const require_toaster = require("./toaster-BbByOytP.cjs");
3
3
  const require_button = require("./button-BxnnE28H.cjs");
4
- const require_search_access = require("./search-access-Dugvttlg.cjs");
4
+ const require_relation_create = require("./relation-create-DDEYcKfw.cjs");
5
5
  const require_skeleton = require("./skeleton-_1gP7wan.cjs");
6
- const require_candidates_gate = require("./candidates-gate-D33jXLHO.cjs");
7
- const require_relation_create = require("./relation-create-IA40t1hi.cjs");
6
+ const require_lazy_relation_picker_dialog = require("./lazy-relation-picker-dialog-BAKBRn3V.cjs");
7
+ const require_relation_select = require("./relation-select-Cr3CfNLy.cjs");
8
+ const require_use_record_subtitle = require("./use-record-subtitle-bfwIoUme.cjs");
8
9
  let _burojs_core = require("@burojs/core");
9
10
  let _burojs_react = require("@burojs/react");
10
11
  let _tabler_icons_react = require("@tabler/icons-react");
@@ -12,7 +13,7 @@ let react = require("react");
12
13
  react = require_use_theme.__toESM(react, 1);
13
14
  let react_jsx_runtime = require("react/jsx-runtime");
14
15
  //#region src/fields/lazy-candidates-picker.tsx
15
- const CandidatesRelationPicker = (0, react.lazy)(() => Promise.resolve().then(() => require("./candidates-relation-picker-Cy0rTxvI.cjs")).then((module) => ({ default: module.CandidatesRelationPicker })));
16
+ const CandidatesRelationPicker = (0, react.lazy)(() => Promise.resolve().then(() => require("./candidates-relation-picker-C47mF260.cjs")).then((module) => ({ default: module.CandidatesRelationPicker })));
16
17
  function LazyCandidatesRelationPicker(props) {
17
18
  return (0, react_jsx_runtime.jsx)(react.Suspense, {
18
19
  fallback: (0, react_jsx_runtime.jsx)(require_skeleton.Skeleton, {
@@ -24,56 +25,16 @@ function LazyCandidatesRelationPicker(props) {
24
25
  });
25
26
  }
26
27
  //#endregion
27
- //#region src/fields/relation-select.ts
28
- function useRelationSelect(input) {
29
- const { to, by, searchField, fixedFilters, scope, scopeField, held, onValue } = input;
30
- const app = (0, _burojs_react.useApp)();
31
- const targetDef = to === "" ? void 0 : app.registry.get(to);
32
- const targetIdField = (targetDef?.data)?.idField;
33
- const idField = typeof targetIdField === "string" && targetIdField.length > 0 ? targetIdField : "id";
34
- const valueField = by ?? idField;
35
- const effectiveSearchField = searchField ?? targetDef?.displayField ?? "name";
36
- const access = (0, _burojs_react.useListAccess)(to);
37
- const searchDecl = react.useMemo(() => require_search_access.narrowSearchDecl(targetDef?.views?.list?.search ?? { fields: [effectiveSearchField] }, (name) => access.fieldVisible(name)), [
38
- targetDef,
39
- effectiveSearchField,
40
- access
41
- ]);
42
- const { create, reader } = require_relation_create.useSelectCreate({
43
- to,
44
- by,
45
- valueField,
46
- idField,
47
- fixedFilters,
48
- scope,
49
- ...scopeField === void 0 ? {} : { scopeField },
50
- held,
51
- searchDecl,
52
- searchContext: {
53
- resource: to,
54
- def: targetDef
55
- },
56
- onValue
57
- });
58
- return {
59
- targetDef,
60
- access,
61
- idField,
62
- valueField,
63
- searchDecl,
64
- create,
65
- reader
66
- };
67
- }
68
- //#endregion
69
28
  //#region src/fields/relation-picker.tsx
70
29
  const DEBOUNCE_MS = 250;
71
- function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, clearable = true, placeholder, name, id, testid, fixedFilters, createSeeds, candidatesGate, rowFits, fieldName, open: openMode, scope = "context", disabled, required, className, ariaInvalid, ariaDescribedBy, ariaErrorMessage }) {
30
+ function RelationPicker({ to: toInput, value, onChange, by, searchField, pageSize = 10, clearable = true, placeholder, name, id, testid, fixedFilters, createSeeds, candidatesGate, rowFits, fieldName, open: openMode, optionSubtitle: optionSubtitle$1, scope = "context", disabled, required, className, ariaInvalid, ariaDescribedBy, ariaErrorMessage }) {
31
+ const to = (0, _burojs_core.routeResourceKey)(toInput);
72
32
  const app = (0, _burojs_react.useApp)();
73
33
  const caps = (0, _burojs_react.useI18nCaps)();
74
34
  const t = (0, _burojs_react.useOptionalT)();
75
35
  const targetDef = app.registry.get(to);
76
- const rowTitle = require_search_access.useRowTitle(to, targetDef?.displayField);
36
+ const rowTitle = require_relation_create.useRowTitle(to, targetDef?.displayField);
37
+ const rowSubtitle = require_use_record_subtitle.useRowSubtitle(to, optionSubtitle$1);
77
38
  const targetLabel = (0, _burojs_core.resolveResourceLabel)(caps, to, "single", targetDef).toLowerCase();
78
39
  const RouterLink = require_toaster.useLinkComponent();
79
40
  const tid = testid ?? `relation-picker:${to}`;
@@ -104,7 +65,7 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
104
65
  }, [open]);
105
66
  const { record: selectedRecord, isLoading: selectedLoading } = (0, _burojs_react.useRelatedRecord)(to, value, by === void 0 ? void 0 : { by });
106
67
  const scopeField = fieldName ?? name;
107
- const { access, valueField, searchDecl, create, reader: createReader } = useRelationSelect({
68
+ const { access, valueField, searchDecl, create, reader: createReader } = require_relation_select.useRelationSelect({
108
69
  to,
109
70
  by,
110
71
  searchField,
@@ -133,7 +94,7 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
133
94
  to,
134
95
  targetDef
135
96
  ]);
136
- const blocked = !accessReady || candidatesGate !== void 0 || require_search_access.namesHiddenField(filter, (name) => access.fieldVisible(name));
97
+ const blocked = !accessReady || candidatesGate !== void 0 || require_relation_create.namesHiddenField(filter, (name) => access.fieldVisible(name));
137
98
  const listQuery = (0, _burojs_react.useList)(to, {
138
99
  ...filter ? { filter } : {},
139
100
  pagination: {
@@ -289,10 +250,10 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
289
250
  className: "px-3 py-6 text-center text-sm text-muted-foreground",
290
251
  "data-testid": candidatesGate.testId,
291
252
  children: candidatesGate.message
292
- }) : (0, react_jsx_runtime.jsxs)(require_search_access.Command, {
253
+ }) : (0, react_jsx_runtime.jsxs)(require_relation_create.Command, {
293
254
  shouldFilter: false,
294
255
  children: [
295
- searchDecl !== void 0 && (0, react_jsx_runtime.jsx)(require_search_access.CommandInput, {
256
+ searchDecl !== void 0 && (0, react_jsx_runtime.jsx)(require_relation_create.CommandInput, {
296
257
  value: search,
297
258
  onValueChange: setSearch,
298
259
  placeholder: t("toolbar.search", {
@@ -305,7 +266,7 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
305
266
  defaultValue: `Search ${targetLabel}`
306
267
  })
307
268
  }),
308
- (0, react_jsx_runtime.jsxs)(require_search_access.CommandList, {
269
+ (0, react_jsx_runtime.jsxs)(require_relation_create.CommandList, {
309
270
  "data-testid": `${childPrefix}:list`,
310
271
  children: [
311
272
  isLoading && (0, react_jsx_runtime.jsxs)("div", {
@@ -316,13 +277,17 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
316
277
  "aria-hidden": true
317
278
  }), (0, react_jsx_runtime.jsx)("span", { children: t("table.loading", { defaultValue: "Loading…" }) })]
318
279
  }),
319
- !isLoading && error && (0, react_jsx_runtime.jsx)("div", {
320
- className: "px-3 py-6 text-center text-sm text-muted-foreground",
321
- "data-testid": `${childPrefix}:error`,
322
- title: error.message,
323
- children: t("relation.failed", { defaultValue: "Failed to load options." })
280
+ !isLoading && error && (0, react_jsx_runtime.jsx)(require_relation_create.LoadError, {
281
+ surface: "picker",
282
+ error,
283
+ testId: `${childPrefix}:error`,
284
+ className: "block px-3 py-6 text-center text-sm text-muted-foreground",
285
+ fallback: {
286
+ key: "relation.failed",
287
+ default: "Failed to load options."
288
+ }
324
289
  }),
325
- !isLoading && !error && rows.length === 0 && (0, react_jsx_runtime.jsx)(require_search_access.CommandEmpty, {
290
+ !isLoading && !error && rows.length === 0 && (0, react_jsx_runtime.jsx)(require_relation_create.CommandEmpty, {
326
291
  "data-testid": `${childPrefix}:empty`,
327
292
  children: t("relation.noResults", { defaultValue: "No results." })
328
293
  }),
@@ -331,8 +296,9 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
331
296
  if (rowId === null || rowId === void 0 || rowId === "") return null;
332
297
  const idStr = String(rowId);
333
298
  const label = rowTitle(row);
299
+ const subtitle = require_use_record_subtitle.optionSubtitle(rowSubtitle(row), label, idStr);
334
300
  const isSelected = value != null && String(value) === idStr;
335
- return (0, react_jsx_runtime.jsxs)(require_search_access.CommandItem, {
301
+ return (0, react_jsx_runtime.jsxs)(require_relation_create.CommandItem, {
336
302
  value: `${label} ${idStr}`,
337
303
  "data-testid": `${childPrefix}:option:${idStr}`,
338
304
  onSelect: () => handleSelect(rowId),
@@ -345,9 +311,9 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
345
311
  className: "truncate",
346
312
  children: label
347
313
  }),
348
- label === idStr ? null : (0, react_jsx_runtime.jsxs)("span", {
314
+ subtitle === null ? null : (0, react_jsx_runtime.jsx)("span", {
349
315
  className: "ml-auto shrink-0 pl-2 text-xs text-muted-foreground",
350
- children: ["#", idStr]
316
+ children: subtitle
351
317
  })
352
318
  ]
353
319
  }, idStr);
@@ -355,7 +321,7 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
355
321
  ]
356
322
  }),
357
323
  (0, react_jsx_runtime.jsxs)("div", {
358
- className: "flex items-center justify-between gap-2 border-t px-3 py-2 text-sm",
324
+ className: "flex flex-wrap items-center justify-between gap-x-2 gap-y-1 border-t px-3 py-2 text-sm",
359
325
  children: [clearable && value != null && value !== "" ? (0, react_jsx_runtime.jsx)("button", {
360
326
  type: "button",
361
327
  onClick: handleClear,
@@ -363,7 +329,7 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
363
329
  className: "text-muted-foreground hover:text-foreground focus:outline-hidden focus-visible:underline",
364
330
  children: t("relationPicker.clear", { defaultValue: "Clear" })
365
331
  }) : (0, react_jsx_runtime.jsx)("span", { "aria-hidden": true }), (0, react_jsx_runtime.jsxs)("div", {
366
- className: "flex items-center gap-3",
332
+ className: "flex flex-wrap items-center gap-x-3 gap-y-1",
367
333
  children: [
368
334
  (0, react_jsx_runtime.jsx)("button", {
369
335
  type: "button",
@@ -399,7 +365,7 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
399
365
  ]
400
366
  })
401
367
  }),
402
- dialogMounted && !gated && (0, react_jsx_runtime.jsx)(require_candidates_gate.LazyRelationPickerDialog, {
368
+ dialogMounted && !gated && (0, react_jsx_runtime.jsx)(require_lazy_relation_picker_dialog.LazyRelationPickerDialog, {
403
369
  to,
404
370
  open: dialogOpen,
405
371
  onOpenChange: setDialogOpen,
@@ -465,6 +431,7 @@ function FieldRelationPicker({ props, to, extra }) {
465
431
  const { name, id, value, onChange, testid, field, disabled, required, relationSeeds } = props;
466
432
  const { ariaInvalid, ariaDescribedBy, ariaErrorMessage } = props;
467
433
  const meta = field?.meta;
434
+ const subtitle = props.optionSubtitle;
468
435
  return (0, react_jsx_runtime.jsx)(RelationPicker, {
469
436
  to,
470
437
  value,
@@ -474,12 +441,14 @@ function FieldRelationPicker({ props, to, extra }) {
474
441
  id,
475
442
  testid: testid ?? `relation-picker:${to}`,
476
443
  searchField: meta?.searchField,
444
+ ...meta?.scope === void 0 ? {} : { scope: meta.scope },
477
445
  fixedFilters: extra === void 0 ? props.fixedFilters : extra.fixedFilters,
478
446
  ...extra?.candidatesGate === void 0 ? {} : { candidatesGate: extra.candidatesGate },
479
447
  ...extra?.rowFits === void 0 ? {} : { rowFits: extra.rowFits },
480
448
  ...relationSeeds === void 0 ? {} : { createSeeds: relationSeeds },
481
449
  fieldName: field?.name ?? name,
482
450
  open: (0, _burojs_core.relationOpenOf)(field ?? {}),
451
+ ...subtitle === void 0 ? {} : { optionSubtitle: subtitle },
483
452
  disabled,
484
453
  required,
485
454
  ariaInvalid,
@@ -506,11 +475,5 @@ Object.defineProperty(exports, "RelationPickerInput", {
506
475
  return RelationPickerInput;
507
476
  }
508
477
  });
509
- Object.defineProperty(exports, "useRelationSelect", {
510
- enumerable: true,
511
- get: function() {
512
- return useRelationSelect;
513
- }
514
- });
515
478
 
516
- //# sourceMappingURL=relation-picker-gag0qSR5.cjs.map
479
+ //# sourceMappingURL=relation-picker-ADjxI_TI.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relation-picker-ADjxI_TI.cjs","names":["lazy","Suspense","Skeleton","routeResourceKey","useApp","useI18nCaps","useOptionalT","useRowTitle","useRowSubtitle","optionSubtitle","resolveResourceLabel","useLinkComponent","React","useRelatedRecord","useRelationSelect","composeListFilter","normalizeFixedFilters","namesHiddenField","useList","Button","cn","IconChevronDown","IconLoader2","Popover","PopoverTrigger","RelationViewButton","PopoverContent","Command","CommandInput","CommandList","LoadError","CommandEmpty","optionSubtitleText","CommandItem","IconCheck","RelationCreateButton","LazyRelationPickerDialog","resourceKey","relationOpenOf"],"sources":["../src/fields/lazy-candidates-picker.tsx","../src/fields/relation-picker.tsx"],"sourcesContent":["import { type ReactElement, Suspense, lazy } from 'react';\nimport type { FieldInputProps } from './registry.js';\nimport { Skeleton } from '../primitives/skeleton.js';\n\nconst CandidatesRelationPicker = lazy(() =>\n import('./candidates-relation-picker.js').then((module) => ({\n default: module.CandidatesRelationPicker,\n })),\n);\n\n/**\n * A relation select whose field declares `candidates`: the candidates path is a chunk of its own,\n * so a form without candidates never loads it. Until it arrives a skeleton stands in; no read\n * is sent for the options meanwhile.\n */\nexport function LazyCandidatesRelationPicker(props: FieldInputProps): ReactElement {\n return (\n <Suspense\n fallback={\n <Skeleton\n data-buro-skeleton=\"\"\n data-testid=\"relation-candidates-loading\"\n className=\"h-9 w-full\"\n />\n }\n >\n <CandidatesRelationPicker {...props} />\n </Suspense>\n );\n}\n","'use client';\n\nimport * as React from 'react';\nimport { IconCheck, IconChevronDown, IconLoader2 } from '@tabler/icons-react';\nimport {\n composeListFilter,\n normalizeFixedFilters,\n resolveResourceLabel,\n resourceKey,\n type ErasedResourceHandle,\n type FilterExpression,\n type FixedFilterInput,\n type RelationOpen,\n relationOpenOf,\n type ResourceRefInput,\n type ScopeSource,\n routeResourceKey,\n type ResourceNameOrRef,\n} from '@burojs/core';\nimport { useApp, useI18nCaps, useList, useOptionalT, useRelatedRecord } from '@burojs/react';\nimport { LoadError } from '../load-error/load-error-view.js';\nimport type { CandidatesGate } from './candidates-gate.js';\nimport { namesHiddenField } from './search-access.js';\nimport {\n optionSubtitle as optionSubtitleText,\n type RecordSubtitleFn,\n useRowSubtitle,\n} from '../resource/use-record-subtitle.js';\nimport { useRowTitle } from '../resource/use-row-title.js';\nimport { cn } from '../lib/cn.js';\nimport { useLinkComponent } from '../layout/link-component.js';\nimport { Button } from '../primitives/button.js';\nimport {\n Command,\n CommandEmpty,\n CommandInput,\n CommandItem,\n CommandList,\n} from '../primitives/command.js';\nimport { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover.js';\nimport { LazyCandidatesRelationPicker } from './lazy-candidates-picker.js';\nimport { LazyRelationPickerDialog } from './lazy-relation-picker-dialog.js';\nimport { RelationCreateButton } from './relation-create.js';\nimport { useRelationSelect } from './relation-select.js';\nimport { RelationViewButton } from './relation-view-button.js';\nimport type { FieldInputProps } from './registry.js';\n\n/** The portable erased AST — the only `fixedFilters` shape this module deals in. */\ntype ErasedExpression = FilterExpression<ErasedResourceHandle>;\n\n/**\n * Single-select relation picker (Relations item 2, v1).\n *\n * Renders a dropdown trigger labelled with the currently-selected\n * record's display name. Clicking opens a popover containing:\n * - a debounced search input that filters target rows by `searchField`\n * (defaults to the target's `displayField`) using the data\n * provider's `contains` operator,\n * - up to `pageSize` (default 10) result rows,\n * - a \"Clear\" button when `clearable` is true and a value is set,\n * - a \"View all\" link to the target's standalone list page so\n * operators can fall back to the full list when the inline search\n * can't find what they need.\n *\n * Multi-select / fuller modal / quick-create are deferred to v2 — see\n * docs/superpowers/specs/2026-05-04-relation-picker.md.\n *\n * Failure modes (mirrors RelationDisplay's fail-soft policy):\n * - target not registered → trigger renders the raw value, picker\n * is disabled. Caller still gets onChange(null) only via clear.\n * - useList errors → popover shows muted \"Failed to load\" with the\n * error message in the title; \"View all\" still works.\n * - target row for `value` not found → trigger shows the raw id\n * (so the operator can at least see what was stored).\n */\n\nexport interface RelationPickerProps<N extends string = string> {\n /** Target resource name (matches `f.relation({ to: '...' })`). */\n to: ResourceNameOrRef<N>;\n /** Currently-selected FK value, or null/undefined when empty. */\n value: string | number | null | undefined;\n /** Called with the new selected id (or `by` value), or null when cleared. */\n onChange: (next: string | number | null) => void;\n /**\n * The target field the value holds (`meta.by`, spec 4.2): options write `row[by]` and the\n * selected value is resolved by it. Absent: the target's id field.\n */\n by?: string;\n /**\n * Field whose value is matched against the search text. Defaults to\n * the target's `displayField` (which is what the trigger label\n * already shows, so it's the natural search target).\n */\n searchField?: string;\n /** Initial fetch / max visible rows. Defaults to 10. */\n pageSize?: number;\n /** Show a clear button. Defaults to true. */\n clearable?: boolean;\n /** Trigger placeholder when value is empty. */\n placeholder?: string;\n /** HTML name attribute for the trigger button. */\n name?: string;\n /** HTML id attribute. */\n id?: string;\n /** Test-id override. Defaults to `relation-picker:<to>`. */\n testid?: string;\n /**\n * Extra filters ANDed onto every query (inline popover + advanced modal) —\n * the same `FixedFilterInput` shape `useResourceList` takes: a bare AST\n * expression (`@burojs/core`'s `FilterExpression`, e.g.\n * `{ field: 'is_active', op: 'eq', value: true }`), or `{ filter, lock?,\n * label? }` for finer control. A bare expression defaults to `lock: true`\n * — the field is hidden from the modal's filter builder, with no badge —\n * which is what keeps the picker from ever offering an out-of-scope\n * record. Pass `{ filter, lock: false }` to keep the field open to the\n * user, and add `label` to render it as an undeletable badge instead.\n */\n fixedFilters?: readonly FixedFilterInput[];\n /**\n * The trusted `defaults` of a create opened from this select (P6-31): the registry field's\n * static `eq` literals, handed down by the form controller. Never read from `fixedFilters` or\n * `field.meta`.\n */\n createSeeds?: Readonly<Record<string, unknown>>;\n /**\n * What the field's `candidates` say about reading the target right now (P7 3.4): `pending`\n * draws a disabled trigger, `empty` a popover that says what to choose first, `forbidden` a\n * disabled trigger and why. While set, nothing is read for the options. Absent: read normally.\n */\n candidatesGate?: CandidatesGate;\n /**\n * Whether a loaded row fits the resolved candidate filters (already in `fixedFilters`):\n * `false` makes the field say the chosen row \"does not match the current filter\" (P7 3.4).\n */\n rowFits?: (row: Record<string, unknown>) => boolean | undefined;\n /** The form field holding the key (the \"view\" and \"+ create\" test ids); defaults to `name`. */\n fieldName?: string;\n /** The field's `ui.open` (P7 3.6): what the \"view\" button opens, or no button for `none`. */\n open?: RelationOpen;\n /**\n * The options' second line over the target row (a relation field's `optionSubtitle`): wins over\n * the target's `recordSubtitle`; without either, `#<id>`.\n */\n optionSubtitle?: RecordSubtitleFn;\n /**\n * The read scope of the option reads (the popover, \"Advanced search\" and the create hand-back).\n * Default `'context'`: the record around the picker narrows its options, except by the field\n * the picker itself edits (`fieldName`, else `name`). `'none'` reads every row the provider\n * allows; `'session'` the session's scope selection.\n */\n scope?: ScopeSource;\n disabled?: boolean;\n required?: boolean;\n className?: string;\n /** Forwarded to the trigger button when the field is in an invalid state. */\n ariaInvalid?: boolean;\n /** Forwarded to the trigger button as `aria-describedby` (help text only; error id goes via ariaErrorMessage). */\n ariaDescribedBy?: string;\n /** Forwarded to the trigger button as `aria-errormessage` (WAI-ARIA 1.2). */\n ariaErrorMessage?: string;\n}\n\nconst DEBOUNCE_MS = 250;\n\nexport function RelationPicker<const N extends string = string>({\n to: toInput,\n value,\n onChange,\n by,\n searchField,\n pageSize = 10,\n clearable = true,\n placeholder,\n name,\n id,\n testid,\n fixedFilters,\n createSeeds,\n candidatesGate,\n rowFits,\n fieldName,\n open: openMode,\n optionSubtitle,\n scope = 'context',\n disabled,\n required,\n className,\n ariaInvalid,\n ariaDescribedBy,\n ariaErrorMessage,\n}: RelationPickerProps<N>): React.ReactElement {\n const to = routeResourceKey(toInput);\n const app = useApp();\n const caps = useI18nCaps();\n const t = useOptionalT();\n const targetDef = app.registry.get(to);\n const rowTitle = useRowTitle(to, targetDef?.displayField);\n const rowSubtitle = useRowSubtitle(to, optionSubtitle);\n // Lowercased: the label sits mid-sentence in «Select location…».\n const targetLabel = resolveResourceLabel(caps, to, 'single', targetDef).toLowerCase();\n const RouterLink = useLinkComponent();\n // Trigger testid honours the prop override (so callers like\n // the record form get a deterministic per-field id like\n // `form-field-input:<resource>:<view>:<field>`), but child elements\n // (search, options, view-all, clear) keep a stable per-target prefix\n // so e2e tests don't have to know the parent form's testid path.\n const tid = testid ?? `relation-picker:${to}`;\n const childPrefix = `relation-picker:${to}`;\n\n const triggerRef = React.useRef<HTMLButtonElement | null>(null);\n // An overlay opened from this select returns focus to the trigger when it closes: the shell\n // records the focused element as it mounts.\n const focusTrigger = React.useCallback(() => triggerRef.current?.focus(), []);\n const [open, setOpen] = React.useState(false);\n const [dialogOpen, setDialogOpen] = React.useState(false);\n // The dialog is mounted on its first open, and only while no candidates gate holds the read:\n // without the candidate filters it would list rows the field must not offer.\n const gated = candidatesGate !== undefined;\n const [dialogMounted, setDialogMounted] = React.useState(false);\n React.useEffect(() => {\n if (gated) setDialogOpen(false);\n }, [gated]);\n React.useEffect(() => {\n if (dialogOpen) setDialogMounted(true);\n }, [dialogOpen]);\n const [search, setSearch] = React.useState('');\n const [debouncedSearch, setDebouncedSearch] = React.useState('');\n\n // Debounce the search text so each keystroke doesn't fire a request —\n // react-query will dedup but the popover can flicker.\n React.useEffect(() => {\n const handle = setTimeout(() => setDebouncedSearch(search), DEBOUNCE_MS);\n return () => clearTimeout(handle);\n }, [search]);\n\n // Reset search when the popover closes so reopening starts clean.\n React.useEffect(() => {\n if (!open) {\n setSearch('');\n setDebouncedSearch('');\n }\n }, [open]);\n\n // Trigger label: fetch the currently-selected target row to show its\n // display name. Skipped when value is empty (useRelatedRecord handles\n // that). Same hook RelationDisplay uses, so the cache is shared.\n // An input resolves the selected value whatever the field's `resolve` says (spec 4.3).\n const { record: selectedRecord, isLoading: selectedLoading } = useRelatedRecord(\n to,\n value,\n by === undefined ? undefined : { by },\n );\n // The record field this picker writes: a record's own scope field never narrows its options.\n const scopeField = fieldName ?? name;\n // The value field, the search set and the select's one pending create, shared by the popover\n // and the \"Advanced search\" dialog (and by `useRelationPicker`).\n const {\n access,\n valueField,\n searchDecl,\n create,\n reader: createReader,\n } = useRelationSelect({\n to,\n by,\n searchField,\n fixedFilters,\n scope,\n scopeField,\n held: gated,\n // As a pick does: the value is written and the \"Advanced search\" dialog, when the create\n // came from it, is done.\n onValue: (picked) => {\n onChange(picked);\n setDialogOpen(false);\n },\n });\n const accessReady = access.status === 'ready';\n // List the picker options. `useList` returns `{ data: { data, total } }`.\n //\n // Composed through the SAME `composeListFilter` core the dialog\n // (`RelationPickerDialog`/`useDialogFilterState`) and the list page use —\n // this popover is a distinct composition SITE (fixed scope + free-text\n // search only, no builder group), not a second set of rules. Search\n // resolution mirrors the dialog's: the target's declared\n // `views.list.search` always wins; the `searchField` prop, else\n // `displayField`, else `'name'` is the fallback (`useRelationSelect`),\n // wrapped as a single-field declaration so it goes through the exact same\n // `searchToFilter` path a real declaration would. Before this, the popover\n // hand-rolled `{ field: <search field>, op: 'contains', value }`\n // unconditionally, ignoring a declared `views.list.search` entirely — one\n // control (popover vs. \"advanced search\") giving two different answers\n // for the same typed text.\n const filter = React.useMemo<ErasedExpression | undefined>(\n () =>\n composeListFilter({\n fixed: normalizeFixedFilters(fixedFilters),\n search: debouncedSearch,\n searchDecl,\n searchContext: { resource: to, def: targetDef },\n }),\n [fixedFilters, debouncedSearch, searchDecl, to, targetDef],\n );\n // An author predicate or a function search naming a hidden field forbids the read.\n const blocked =\n !accessReady ||\n candidatesGate !== undefined ||\n namesHiddenField(filter, (name) => access.fieldVisible(name));\n\n const listQuery = useList(to, {\n ...(filter ? { filter } : {}),\n pagination: { kind: 'offset', page: 1, pageSize },\n scope,\n ...(scopeField === undefined ? {} : { scopeField }),\n enabled: open && targetDef != null && !blocked,\n });\n\n const rows = (listQuery.data?.data ?? []) as Array<{ id: unknown } & Record<string, unknown>>;\n // ⚠️ The rows the popover already loaded are a better label source than a\n // second round-trip. `selectedRecord` comes from the batched relation\n // aggregator; immediately after picking an option that batch has not\n // resolved the new id yet, so the trigger fell through to its raw-id\n // fallback and showed «4» instead of «Finished goods warehouse». The picker\n // is holding the chosen record in `rows` — use it.\n const triggerLabel = renderTriggerLabel({\n rowTitle,\n optionRows: rows,\n valueField,\n value,\n selectedRecord,\n selectedLoading,\n targetDef,\n targetLabel,\n placeholder,\n t,\n });\n\n // Target not registered — degrade gracefully: render a disabled\n // trigger with the raw value so the operator can still SEE the\n // stored FK, but they can't pick a new one because we have no\n // basePath to drive a \"View all\" link or a useList query.\n if (!targetDef) {\n if (process.env.NODE_ENV !== 'production') {\n // eslint-disable-next-line no-console\n console.warn(`[RelationPicker] target resource '${to}' not registered — picker disabled`);\n }\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={`${childPrefix}:unregistered`}\n className={cn('w-full justify-between font-normal', className)}\n name={name}\n id={id}\n aria-required={required}\n >\n <span className=\"truncate text-muted-foreground\">\n {value == null || value === ''\n ? (placeholder ?? t('field.select', { defaultValue: 'Select…' }))\n : String(value)}\n </span>\n <IconChevronDown className=\"h-4 w-4 opacity-50\" aria-hidden />\n </Button>\n );\n }\n\n // The candidates say the target must not be read yet (P7 3.4): a disabled trigger, no popover.\n if (candidatesGate?.kind === 'pending') {\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={tid}\n data-buro-candidates=\"pending\"\n className={cn('w-full justify-between font-normal', className)}\n name={name}\n id={id}\n aria-required={required}\n aria-busy=\"true\"\n >\n <span className=\"truncate text-muted-foreground\">{triggerLabel}</span>\n <IconLoader2 className=\"h-4 w-4 shrink-0 animate-spin opacity-50\" aria-hidden />\n </Button>\n );\n }\n if (candidatesGate?.kind === 'forbidden') {\n return (\n <div className=\"flex flex-col gap-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={tid}\n data-buro-candidates=\"forbidden\"\n className={cn('w-full justify-between font-normal', className)}\n name={name}\n id={id}\n aria-required={required}\n aria-describedby={candidatesGate.testId}\n >\n <span className={cn('truncate text-left', value == null && 'text-muted-foreground')}>\n {triggerLabel}\n </span>\n <IconChevronDown className=\"h-4 w-4 shrink-0 opacity-50\" aria-hidden />\n </Button>\n <p\n id={candidatesGate.testId}\n data-testid={candidatesGate.testId}\n className=\"text-xs text-muted-foreground\"\n >\n {candidatesGate.message}\n </p>\n </div>\n );\n }\n\n const targetBasePath = targetDef.basePath ?? `/${to}`;\n // The chosen row stays as it is when it falls outside the candidates; the field says so.\n const outside =\n rowFits !== undefined &&\n value != null &&\n value !== '' &&\n selectedRecord != null &&\n rowFits(selectedRecord as Record<string, unknown>) === false;\n const isLoading = listQuery.isLoading || listQuery.isFetching;\n const error = listQuery.isError ? (listQuery.error as unknown as Error) : null;\n\n function handleSelect(rowId: unknown): void {\n // IDs arrive as strings or numbers depending on the data provider.\n // Pass through as-is so callers can compare to their\n // schema (e.g. UUID strings stay strings).\n onChange(rowId as string | number);\n setOpen(false);\n }\n\n function handleClear(): void {\n onChange(null);\n setOpen(false);\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <div className=\"flex items-center gap-1\">\n <PopoverTrigger asChild>\n <Button\n ref={triggerRef}\n type=\"button\"\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-required={required}\n aria-invalid={ariaInvalid || undefined}\n aria-describedby={ariaDescribedBy}\n {...(ariaErrorMessage ? { 'aria-errormessage': ariaErrorMessage } : {})}\n disabled={disabled}\n data-testid={tid}\n name={name}\n id={id}\n className={cn('w-full justify-between font-normal', className)}\n >\n <span className={cn('truncate text-left', value == null && 'text-muted-foreground')}>\n {triggerLabel}\n </span>\n <IconChevronDown className=\"h-4 w-4 shrink-0 opacity-50\" aria-hidden />\n </Button>\n </PopoverTrigger>\n {!disabled && (\n <RelationViewButton\n to={to}\n value={value}\n by={by}\n fieldName={fieldName ?? name ?? to}\n beforeOpen={focusTrigger}\n {...(openMode === undefined ? {} : { open: openMode })}\n />\n )}\n </div>\n {outside && (\n <p\n className=\"mt-1 text-xs text-muted-foreground\"\n data-testid={`${childPrefix}:not-matching`}\n >\n {t('relationPicker.notMatching', { defaultValue: 'Does not match the current filter' })}\n </p>\n )}\n <PopoverContent className=\"w-[var(--radix-popover-trigger-width)] p-0\" align=\"start\">\n {candidatesGate?.kind === 'empty' ? (\n <p\n className=\"px-3 py-6 text-center text-sm text-muted-foreground\"\n data-testid={candidatesGate.testId}\n >\n {candidatesGate.message}\n </p>\n ) : (\n /* shouldFilter={false}: cmdk's built-in client filter is disabled\n because filtering happens server-side via useList's `contains`\n operator. cmdk still drives keyboard navigation, focus,\n highlighted-item state, and Enter-to-select. */\n <Command shouldFilter={false}>\n {searchDecl !== undefined && (\n <CommandInput\n value={search}\n onValueChange={setSearch}\n placeholder={t('toolbar.search', {\n params: { name: targetLabel },\n defaultValue: `Search ${targetLabel}…`,\n })}\n data-testid={`${childPrefix}:search`}\n aria-label={t('toolbar.search', {\n params: { name: targetLabel },\n defaultValue: `Search ${targetLabel}`,\n })}\n />\n )}\n <CommandList data-testid={`${childPrefix}:list`}>\n {isLoading && (\n <div\n className=\"flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground\"\n data-testid={`${childPrefix}:loading`}\n >\n <IconLoader2 className=\"h-4 w-4 animate-spin\" aria-hidden />\n <span>{t('table.loading', { defaultValue: 'Loading…' })}</span>\n </div>\n )}\n {!isLoading && error && (\n <LoadError\n surface=\"picker\"\n error={error}\n testId={`${childPrefix}:error`}\n className=\"block px-3 py-6 text-center text-sm text-muted-foreground\"\n fallback={{ key: 'relation.failed', default: 'Failed to load options.' }}\n />\n )}\n {!isLoading && !error && rows.length === 0 && (\n <CommandEmpty data-testid={`${childPrefix}:empty`}>\n {t('relation.noResults', { defaultValue: 'No results.' })}\n </CommandEmpty>\n )}\n {!isLoading &&\n !error &&\n rows.map((row) => {\n const rowId = row[valueField];\n // A row without the value the field stores cannot be chosen (no fallback to id).\n if (rowId === null || rowId === undefined || rowId === '') return null;\n const idStr = String(rowId);\n const label = rowTitle(row);\n const subtitle = optionSubtitleText(rowSubtitle(row), label, idStr);\n const isSelected = value != null && String(value) === idStr;\n return (\n <CommandItem\n key={idStr}\n // cmdk uses `value` for keyboard-search semantics;\n // setting it to `${label} ${idStr}` lets users type\n // either the label OR the id and the highlighted\n // option still tracks correctly across renders.\n value={`${label} ${idStr}`}\n data-testid={`${childPrefix}:option:${idStr}`}\n onSelect={() => handleSelect(rowId)}\n >\n <IconCheck\n className={cn(\n 'mr-2 h-4 w-4 shrink-0',\n isSelected ? 'opacity-100' : 'opacity-0',\n )}\n aria-hidden\n />\n <span className=\"truncate\">{label}</span>\n {/* The declared subtitle, else `#<id>` unless the label already is the id\n (REF-40). */}\n {subtitle === null ? null : (\n <span className=\"ml-auto shrink-0 pl-2 text-xs text-muted-foreground\">\n {subtitle}\n </span>\n )}\n </CommandItem>\n );\n })}\n </CommandList>\n {/* Wraps at narrow widths: four actions do not fit one line of a small popover. */}\n <div className=\"flex flex-wrap items-center justify-between gap-x-2 gap-y-1 border-t px-3 py-2 text-sm\">\n {clearable && value != null && value !== '' ? (\n <button\n type=\"button\"\n onClick={handleClear}\n data-testid={`${childPrefix}:clear`}\n className=\"text-muted-foreground hover:text-foreground focus:outline-hidden focus-visible:underline\"\n >\n {t('relationPicker.clear', { defaultValue: 'Clear' })}\n </button>\n ) : (\n <span aria-hidden />\n )}\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1\">\n {/* #11 — escalation to the full-list modal: search + sort +\n paginate + create, for when the inline popover isn't enough. */}\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n setDialogOpen(true);\n }}\n data-testid={`${childPrefix}:advanced`}\n className=\"text-foreground hover:underline focus:outline-hidden focus-visible:underline\"\n >\n {t('relationPicker.advancedSearch', { defaultValue: 'Advanced search' })}\n </button>\n {!disabled && (\n <RelationCreateButton\n to={to}\n fieldName={fieldName ?? name ?? to}\n create={create}\n seeds={createSeeds}\n search={search}\n beforeOpen={() => {\n setOpen(false);\n focusTrigger();\n }}\n className=\"text-foreground hover:underline focus:outline-hidden focus-visible:underline disabled:opacity-50\"\n />\n )}\n <RouterLink\n href={targetBasePath}\n data-testid={`${childPrefix}:view-all`}\n className=\"text-muted-foreground hover:text-foreground hover:underline focus:outline-hidden focus-visible:underline\"\n >\n {t('relationPicker.viewAll', { defaultValue: 'Open list' })} →\n </RouterLink>\n </div>\n </div>\n </Command>\n )}\n </PopoverContent>\n {/*\n The RAW `searchField` prop, not the resolved fallback — the dialog\n (Task 5, SP3) does its OWN target-declaration → prop → displayField\n resolution via `useDialogFilterState`, so handing it an already-\n resolved value here would just have this component's own\n `displayField ?? 'name'` fallback (built for the POPOVER's simpler\n contains-only search) silently stand in for the dialog's, rather\n than letting the target's declared `views.list.search` win when the\n caller passed no override at all.\n */}\n {dialogMounted && !gated && (\n <LazyRelationPickerDialog\n to={to}\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n onSelect={(rowId) => onChange(rowId)}\n valueField={valueField}\n searchField={searchField}\n fixedFilters={fixedFilters}\n scope={scope}\n {...(scopeField === undefined ? {} : { scopeField })}\n {...(disabled ? {} : { create })}\n createSeeds={createSeeds}\n fieldName={fieldName ?? name ?? to}\n onBeforeCreate={focusTrigger}\n />\n )}\n {createReader}\n </Popover>\n );\n}\n\ninterface TriggerLabelArgs {\n value: string | number | null | undefined;\n selectedRecord: ReturnType<typeof useRelatedRecord>['record'];\n selectedLoading: boolean;\n /** Rows already loaded for the popover; used as a same-tick label source. */\n optionRows?: Array<{ id: unknown } & Record<string, unknown>>;\n /** The row field the value holds (`by`, else the id field). */\n valueField: string;\n targetDef: ReturnType<ReturnType<typeof useApp>['registry']['get']>;\n /** The resolved, lowercased target label. */\n targetLabel: string;\n placeholder?: string;\n /** The row title under the viewer's field view (id when the display field is hidden). */\n rowTitle: (row: Record<string, unknown>) => string;\n t: (key: string, options?: { params?: Record<string, unknown>; defaultValue?: string }) => string;\n}\n\nfunction renderTriggerLabel({\n value,\n selectedRecord,\n selectedLoading,\n targetLabel,\n placeholder,\n optionRows,\n valueField,\n t,\n rowTitle,\n}: TriggerLabelArgs): React.ReactNode {\n if (value == null || value === '') {\n return (\n placeholder ??\n t('relation.select', {\n params: { name: targetLabel },\n defaultValue: `Select ${targetLabel}…`,\n })\n );\n }\n if (selectedLoading) {\n return t('table.loading', { defaultValue: 'Loading…' });\n }\n if (selectedRecord) {\n return rowTitle(selectedRecord);\n }\n // Not resolved by the aggregator yet (or at all). If the popover already\n // loaded this record as an option — which it always has right after the\n // user picked it — label from that instead of showing a bare id.\n const fromOptions = optionRows?.find((r) => String(r[valueField]) === String(value));\n if (fromOptions) {\n return rowTitle(fromOptions);\n }\n // Genuinely unknown (404 / not in the loaded page) — show the raw id so the\n // operator can at least see what was stored.\n return String(value);\n}\n\n/**\n * Form-field adapter wrapping `<RelationPicker>` for use as a default\n * Input renderer in the field registry. Reads `field.meta.to` for the\n * target resource name. When `meta.to` is absent we degrade to a plain\n * (disabled) text representation of the value with a console warning —\n * same fail-soft as RelationDisplay.\n */\nexport function RelationPickerInput(props: FieldInputProps): React.ReactElement {\n const { name, id, value, testid, field, required } = props;\n const meta = field?.meta as { to?: ResourceRefInput; candidates?: unknown } | undefined;\n let to: string | undefined;\n try {\n to = meta?.to === undefined ? undefined : resourceKey(meta.to);\n } catch {\n to = undefined;\n }\n\n if (!to) {\n if (process.env.NODE_ENV !== 'production') {\n // eslint-disable-next-line no-console\n console.warn(\n `[RelationPickerInput] no meta.to on field '${field?.name ?? name}' — relation picker disabled`,\n );\n }\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={`${testid ?? `relation-picker:${name}`}:no-target`}\n className=\"w-full justify-between font-normal\"\n name={name}\n id={id}\n aria-required={required}\n >\n <span className=\"truncate text-muted-foreground\">\n {value == null || value === '' ? '—' : String(value)}\n </span>\n </Button>\n );\n }\n\n // The candidates path is loaded only for a field that declares candidates.\n if (Array.isArray(meta?.candidates) && meta.candidates.length > 0)\n return <LazyCandidatesRelationPicker {...props} />;\n return <FieldRelationPicker props={props} to={to} />;\n}\n\n/** The form field's props as `RelationPicker` takes them; `extra` carries the candidates' part. */\nexport function FieldRelationPicker({\n props,\n to,\n extra,\n}: {\n props: FieldInputProps;\n to: string;\n extra?: {\n fixedFilters: FieldInputProps['fixedFilters'];\n candidatesGate?: CandidatesGate;\n rowFits?: (row: Record<string, unknown>) => boolean | undefined;\n };\n}): React.ReactElement {\n const { name, id, value, onChange, testid, field, disabled, required, relationSeeds } = props;\n const { ariaInvalid, ariaDescribedBy, ariaErrorMessage } = props;\n const meta = field?.meta as\n | { by?: unknown; searchField?: string; scope?: ScopeSource }\n | undefined;\n const subtitle = props.optionSubtitle;\n return (\n <RelationPicker\n to={to}\n value={value as string | number | null | undefined}\n onChange={(v) => onChange(v)}\n {...(typeof meta?.by === 'string' ? { by: meta.by } : {})}\n name={name}\n id={id}\n testid={testid ?? `relation-picker:${to}`}\n searchField={meta?.searchField}\n {...(meta?.scope === undefined ? {} : { scope: meta.scope })}\n fixedFilters={extra === undefined ? props.fixedFilters : extra.fixedFilters}\n {...(extra?.candidatesGate === undefined ? {} : { candidatesGate: extra.candidatesGate })}\n {...(extra?.rowFits === undefined ? {} : { rowFits: extra.rowFits })}\n {...(relationSeeds === undefined ? {} : { createSeeds: relationSeeds })}\n fieldName={field?.name ?? name}\n open={relationOpenOf(field ?? {})}\n {...(subtitle === undefined ? {} : { optionSubtitle: subtitle })}\n disabled={disabled}\n required={required}\n ariaInvalid={ariaInvalid}\n ariaDescribedBy={ariaDescribedBy}\n ariaErrorMessage={ariaErrorMessage}\n />\n );\n}\n"],"mappings":"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"}
@@ -1,29 +1,34 @@
1
1
  import { l as cn } from "./use-theme-dnE3Od3I.js";
2
2
  import { t as Button } from "./button-MWVeZaqj.js";
3
3
  import { t as Input } from "./input-CUPStzZO.js";
4
- import { a as DialogFooter, i as DialogDescription, o as DialogHeader, r as DialogContent, s as DialogTitle, t as Dialog } from "./dialog-B_rt9S1c.js";
5
- import { d as useRowTitle } from "./search-access-CPPmfuvy.js";
6
- import { C as TableHeader, S as TableHead, t as FilterBuilder, v as Table, w as TableRow, x as TableCell, y as TableBody } from "./filter-builder-Bhn-dhZV.js";
7
- import { t as RelationCreateButton } from "./relation-create-h6F9B4C4.js";
8
- import { t as useDialogFilterState } from "./use-dialog-filter-state-C--oMAZG.js";
9
- import { declaredText, enumNamespaceOf, enumValueText, fieldOptionText, resolveResourceLabel } from "@burojs/core";
10
- import { useApp, useI18nCaps, useList, useListAccess, useOptionalT } from "@burojs/react";
4
+ import { a as DialogFooter, i as DialogDescription, o as DialogHeader, r as DialogContent, s as DialogTitle, t as Dialog } from "./dialog-C123sVQ8.js";
5
+ import { m as useRowTitle, t as RelationCreateButton } from "./relation-create-C43cdPQt.js";
6
+ import { A as TableHeader, E as TableBody, F as ResolvedFieldDisplay, O as TableCell, T as Table, j as TableRow, k as TableHead, t as FilterBuilder } from "./filter-builder-z3Axzf9Y.js";
7
+ import { t as useDialogFilterState } from "./use-dialog-filter-state-B_MqzHaT.js";
8
+ import { columnName, declaredField, declaredText, enumNamespaceOf, enumValueText, fieldOptionText, resolveFieldLabel, resolveResourceLabel, routeResourceKey } from "@burojs/core";
9
+ import { recordKeyOf, useApp, useChromeT, useI18nCaps, useList, useListAccess, useOptionalT } from "@burojs/react";
11
10
  import { IconArrowDown, IconArrowUp, IconCheck, IconChevronRight, IconLoader2, IconSearch, IconSelector } from "@tabler/icons-react";
12
11
  import * as React from "react";
13
12
  import { jsx, jsxs } from "react/jsx-runtime";
14
13
  //#region src/fields/relation-picker-dialog.tsx
15
14
  const noop = () => void 0;
16
15
  const DEBOUNCE_MS = 250;
17
- function RelationPickerDialog({ to, open, onOpenChange, onSelect, onSelectRow, searchField, valueField = "id", fixedFilters, scope = "context", scopeField, create, createSeeds, fieldName, onBeforeCreate, pageSize = 8, multiple = false, selected, onToggle, onDone }) {
16
+ function RelationPickerDialog({ to: toInput, open, onOpenChange, onSelect, onSelectRow, searchField, valueField: valueFieldProp, fixedFilters, scope = "context", scopeField, create, createSeeds, fieldName, onBeforeCreate, pageSize = 8, multiple = false, selected, onToggle, onDone, disabledKeys, disabledLabel }) {
17
+ const to = routeResourceKey(toInput);
18
18
  const app = useApp();
19
19
  const caps = useI18nCaps();
20
+ const targetCaps = useI18nCaps(to);
20
21
  const t = useOptionalT();
22
+ const chromeT = useChromeT();
21
23
  const tr = (key, params, fallback) => t?.(key, {
22
24
  params,
23
25
  defaultValue: fallback
24
26
  }) ?? fallback ?? key;
25
27
  const targetDef = app.registry.get(to);
26
28
  const rowTitle = useRowTitle(to, targetDef?.displayField);
29
+ const declaredIdField = (targetDef?.data)?.idField;
30
+ const idField = typeof declaredIdField === "string" && declaredIdField.length > 0 ? declaredIdField : "id";
31
+ const valueField = valueFieldProp ?? idField;
27
32
  const [search, setSearchInput] = React.useState("");
28
33
  const [debounced, setDebounced] = React.useState("");
29
34
  const [sort, setSort] = React.useState({
@@ -52,7 +57,7 @@ function RelationPickerDialog({ to, open, onOpenChange, onSelect, onSelectRow, s
52
57
  }, [open]);
53
58
  const listAccess = useListAccess(to);
54
59
  const extraColumns = React.useMemo(() => {
55
- return (targetDef?.views?.list?.columns ?? []).filter((c) => c !== "id" && c !== displayField).filter((c) => listAccess.status === "ready" && listAccess.columnVisible(c)).slice(0, 2);
60
+ return (targetDef?.views?.list?.columns ?? []).map(columnName).filter((c) => c !== "id" && c !== displayField).filter((c) => listAccess.status === "ready" && listAccess.columnVisible(c)).slice(0, 2);
56
61
  }, [
57
62
  targetDef,
58
63
  displayField,
@@ -82,6 +87,42 @@ function RelationPickerDialog({ to, open, onOpenChange, onSelect, onSelectRow, s
82
87
  const pageIsFull = rows.length >= pageSize;
83
88
  const hasNextPage = knownTotal ? filterState.page < totalPages : pageIsFull;
84
89
  const isLoading = query.isLoading || query.isFetching;
90
+ const keyOf = (row) => valueField === idField ? recordKeyOf(targetDef, row) : row[valueField];
91
+ const pageKeys = rows.map(keyOf).filter((key) => typeof key === "string" || typeof key === "number");
92
+ const asked = JSON.stringify(pageKeys);
93
+ const [disabled, setDisabled] = React.useState(void 0);
94
+ const [attempt, setAttempt] = React.useState(0);
95
+ React.useEffect(() => {
96
+ if (disabledKeys === void 0 || pageKeys.length === 0) return;
97
+ const controller = new AbortController();
98
+ disabledKeys(pageKeys, controller.signal).then((keys) => {
99
+ if (!controller.signal.aborted) setDisabled({
100
+ asked,
101
+ attempt,
102
+ keys
103
+ });
104
+ }, () => {
105
+ if (!controller.signal.aborted) setDisabled({
106
+ asked,
107
+ attempt,
108
+ keys: "failed"
109
+ });
110
+ });
111
+ return () => controller.abort();
112
+ }, [
113
+ asked,
114
+ attempt,
115
+ disabledKeys
116
+ ]);
117
+ const answer = disabledKeys === void 0 || pageKeys.length === 0 || disabled === void 0 || disabled.asked !== asked || disabled.attempt !== attempt ? void 0 : disabled.keys;
118
+ const checking = disabledKeys !== void 0 && pageKeys.length > 0 && answer === void 0;
119
+ const checkFailed = answer === "failed";
120
+ const isDisabled = (row) => {
121
+ if (disabledKeys === void 0) return false;
122
+ if (answer === void 0 || answer === "failed") return true;
123
+ return answer.has(String(keyOf(row)));
124
+ };
125
+ const disabledCount = answer === void 0 || answer === "failed" ? 0 : rows.filter(isDisabled).length;
85
126
  const toggleSort = (field) => {
86
127
  setSort((prev) => prev.field === field ? {
87
128
  field,
@@ -91,14 +132,11 @@ function RelationPickerDialog({ to, open, onOpenChange, onSelect, onSelectRow, s
91
132
  dir: "asc"
92
133
  });
93
134
  };
94
- const SortIcon = ({ field }) => {
95
- if (sort.field !== field) return jsx(IconSelector, { className: "ml-1 inline size-3 opacity-40" });
96
- return sort.dir === "asc" ? jsx(IconArrowUp, { className: "ml-1 inline size-3" }) : jsx(IconArrowDown, { className: "ml-1 inline size-3" });
97
- };
98
135
  const targetLabel = resolveResourceLabel(caps, to, "single", targetDef);
99
136
  const colCount = 2 + extraColumns.length;
100
137
  const pick = (row) => {
101
- const picked = valueField === "id" ? row.id : row[valueField];
138
+ if (isDisabled(row)) return;
139
+ const picked = keyOf(row);
102
140
  if (picked === null || picked === void 0 || picked === "") return;
103
141
  if (multiple) {
104
142
  onToggle?.(picked, row);
@@ -161,7 +199,14 @@ function RelationPickerDialog({ to, open, onOpenChange, onSelect, onSelectRow, s
161
199
  jsxs(TableHead, {
162
200
  className: "h-9 w-14 cursor-pointer select-none",
163
201
  onClick: () => toggleSort("id"),
164
- children: ["ID ", jsx(SortIcon, { field: "id" })]
202
+ children: [
203
+ tr("relationPicker.idColumn", void 0, "ID"),
204
+ " ",
205
+ jsx(SortIcon, {
206
+ field: "id",
207
+ sort
208
+ })
209
+ ]
165
210
  }),
166
211
  jsxs(TableHead, {
167
212
  className: cn("h-9 select-none", titleSortable && "cursor-pointer"),
@@ -169,13 +214,19 @@ function RelationPickerDialog({ to, open, onOpenChange, onSelect, onSelectRow, s
169
214
  children: [
170
215
  targetLabel,
171
216
  " ",
172
- titleSortable && jsx(SortIcon, { field: displayField })
217
+ titleSortable && jsx(SortIcon, {
218
+ field: displayField,
219
+ sort
220
+ })
173
221
  ]
174
222
  }),
175
223
  extraColumns.map((c) => jsxs(TableHead, {
176
224
  className: "h-9 cursor-pointer select-none",
177
225
  onClick: () => toggleSort(c),
178
- children: [declaredText("field", targetDef?.fields?.[c], "label") ?? c, jsx(SortIcon, { field: c })]
226
+ children: [resolveFieldLabel(targetCaps, c, declaredText("field", targetDef?.fields?.[c], "label")), jsx(SortIcon, {
227
+ field: c,
228
+ sort
229
+ })]
179
230
  }, c)),
180
231
  jsx(TableHead, { className: "h-9 w-8" })
181
232
  ]
@@ -195,16 +246,19 @@ function RelationPickerDialog({ to, open, onOpenChange, onSelect, onSelectRow, s
195
246
  children: tr("relation.noResults", void 0, "No results.")
196
247
  })
197
248
  }) : rows.map((row) => {
198
- const idStr = String(row.id);
249
+ const idStr = String(recordKeyOf(targetDef, row));
250
+ const rowDisabled = isDisabled(row);
251
+ const checked = rowDisabled || (selected ?? []).some((key) => String(key) === String(keyOf(row)));
199
252
  return jsxs(TableRow, {
200
- className: "cursor-pointer",
253
+ className: cn(rowDisabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"),
201
254
  "data-testid": `relation-picker-dialog:${to}:row:${idStr}`,
202
- ...multiple ? { "aria-selected": (selected ?? []).some((key) => String(key) === String(valueField === "id" ? row.id : row[valueField])) } : {},
255
+ ...multiple ? { "aria-selected": checked } : {},
256
+ ...rowDisabled ? { "aria-disabled": true } : {},
203
257
  onClick: () => pick(row),
204
258
  children: [
205
- jsxs(TableCell, {
259
+ jsx(TableCell, {
206
260
  className: "py-2 tabular-nums text-muted-foreground",
207
- children: ["#", idStr]
261
+ children: tr("relationPicker.recordNumber", { id: idStr }, `#${idStr}`)
208
262
  }),
209
263
  jsx(TableCell, {
210
264
  className: "py-2 font-medium",
@@ -212,12 +266,22 @@ function RelationPickerDialog({ to, open, onOpenChange, onSelect, onSelectRow, s
212
266
  }),
213
267
  extraColumns.map((c) => jsx(TableCell, {
214
268
  className: "py-2 text-muted-foreground",
215
- children: formatCell(row[c], targetDef?.fields?.[c], (ns) => app.registry.enums.get(ns))
269
+ children: jsx(ResolvedFieldDisplay, {
270
+ resource: targetDef?.ref ?? {
271
+ namespace: "default",
272
+ id: to
273
+ },
274
+ name: c,
275
+ field: (targetDef === void 0 ? void 0 : declaredField(targetDef, c)) ?? targetDef?.fields?.[c] ?? { type: "unknown" },
276
+ value: row[c],
277
+ record: row,
278
+ fallback: formatCell(row[c], targetDef?.fields?.[c], (ns) => app.registry.enums.get(ns))
279
+ })
216
280
  }, c)),
217
281
  jsx(TableCell, {
218
282
  className: "py-2 text-right",
219
283
  children: multiple ? jsx(IconCheck, {
220
- className: cn("ml-auto size-4", (selected ?? []).some((key) => String(key) === String(valueField === "id" ? row.id : row[valueField])) ? "opacity-100" : "opacity-0"),
284
+ className: cn("ml-auto size-4", checked ? "opacity-100" : "opacity-0"),
221
285
  "aria-hidden": true
222
286
  }) : jsx(IconChevronRight, { className: "ml-auto size-4 text-muted-foreground/50" })
223
287
  })
@@ -227,17 +291,47 @@ function RelationPickerDialog({ to, open, onOpenChange, onSelect, onSelectRow, s
227
291
  }),
228
292
  jsxs(DialogFooter, {
229
293
  className: "flex-row items-center justify-between gap-2 border-t px-4 py-2.5 sm:justify-between",
230
- children: [jsx("div", {
231
- className: "flex items-center",
232
- children: create !== void 0 && jsx(RelationCreateButton, {
233
- to,
234
- fieldName: fieldName ?? to,
235
- create,
236
- seeds: createSeeds,
237
- search,
238
- beforeOpen: onBeforeCreate ?? noop,
239
- className: "inline-flex items-center gap-1 whitespace-nowrap text-sm text-foreground hover:underline disabled:opacity-50"
240
- })
294
+ children: [jsxs("div", {
295
+ className: "flex items-center gap-3",
296
+ children: [
297
+ checking && jsxs("span", {
298
+ role: "status",
299
+ className: "inline-flex items-center gap-1.5 text-sm text-muted-foreground",
300
+ "data-testid": `relation-picker-dialog:${to}:disabled-checking`,
301
+ children: [jsx(IconLoader2, {
302
+ className: "size-3.5 animate-spin",
303
+ "aria-hidden": "true"
304
+ }), tr("relationPicker.checking", void 0, "Checking these rows…")]
305
+ }),
306
+ checkFailed && jsxs("span", {
307
+ role: "alert",
308
+ className: "inline-flex items-center gap-2 text-sm text-destructive",
309
+ "data-testid": `relation-picker-dialog:${to}:disabled-failed`,
310
+ children: [tr("relationPicker.checkFailed", void 0, "These rows could not be checked, so none can be chosen."), jsx(Button, {
311
+ type: "button",
312
+ size: "sm",
313
+ variant: "outline",
314
+ className: "h-7",
315
+ onClick: () => setAttempt((n) => n + 1),
316
+ "data-testid": `relation-picker-dialog:${to}:disabled-retry`,
317
+ children: chromeT("actions.retry", { defaultValue: "Retry" })
318
+ })]
319
+ }),
320
+ disabledCount > 0 && disabledLabel !== void 0 && jsx("span", {
321
+ className: "text-sm text-muted-foreground",
322
+ "data-testid": `relation-picker-dialog:${to}:disabled-count`,
323
+ children: disabledLabel(disabledCount)
324
+ }),
325
+ create !== void 0 && jsx(RelationCreateButton, {
326
+ to,
327
+ fieldName: fieldName ?? to,
328
+ create,
329
+ seeds: createSeeds,
330
+ search,
331
+ beforeOpen: onBeforeCreate ?? noop,
332
+ className: "inline-flex items-center gap-1 whitespace-nowrap text-sm text-foreground hover:underline disabled:opacity-50"
333
+ })
334
+ ]
241
335
  }), jsxs("div", {
242
336
  className: "flex items-center gap-2 text-sm text-muted-foreground",
243
337
  children: [
@@ -283,6 +377,10 @@ function RelationPickerDialog({ to, open, onOpenChange, onSelect, onSelectRow, s
283
377
  })
284
378
  });
285
379
  }
380
+ function SortIcon({ field, sort }) {
381
+ if (sort.field !== field) return jsx(IconSelector, { className: "ml-1 inline size-3 opacity-40" });
382
+ return sort.dir === "asc" ? jsx(IconArrowUp, { className: "ml-1 inline size-3" }) : jsx(IconArrowDown, { className: "ml-1 inline size-3" });
383
+ }
286
384
  function formatCell(value, field, enumOf) {
287
385
  if (value == null || value === "") return "—";
288
386
  if (typeof value === "boolean") return value ? "✓" : "—";
@@ -300,4 +398,4 @@ function formatCell(value, field, enumOf) {
300
398
  //#endregion
301
399
  export { RelationPickerDialog };
302
400
 
303
- //# sourceMappingURL=relation-picker-dialog-B6nIiJBf.js.map
401
+ //# sourceMappingURL=relation-picker-dialog-CyKliBkN.js.map