@burojs/ui-shadcn 0.3.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.
Files changed (179) hide show
  1. package/README.md +26 -7
  2. package/dist/bulk-summary-CwSd6Yjb.cjs +108 -0
  3. package/dist/bulk-summary-CwSd6Yjb.cjs.map +1 -0
  4. package/dist/bulk-summary-GxMDP4bw.js +108 -0
  5. package/dist/bulk-summary-GxMDP4bw.js.map +1 -0
  6. package/dist/button-BxnnE28H.cjs +55 -0
  7. package/dist/button-BxnnE28H.cjs.map +1 -0
  8. package/dist/button-MWVeZaqj.js +44 -0
  9. package/dist/button-MWVeZaqj.js.map +1 -0
  10. package/dist/candidates-gate-CX2PBu6l.js +63 -0
  11. package/dist/candidates-gate-CX2PBu6l.js.map +1 -0
  12. package/dist/candidates-gate-DiFI52r7.cjs +70 -0
  13. package/dist/candidates-gate-DiFI52r7.cjs.map +1 -0
  14. package/dist/candidates-relation-picker-CBsVcnPR.js +82 -0
  15. package/dist/candidates-relation-picker-CBsVcnPR.js.map +1 -0
  16. package/dist/candidates-relation-picker-Dd_GVLlu.cjs +84 -0
  17. package/dist/candidates-relation-picker-Dd_GVLlu.cjs.map +1 -0
  18. package/dist/check-confirm-dialog-BRByTFpg.cjs +1534 -0
  19. package/dist/check-confirm-dialog-BRByTFpg.cjs.map +1 -0
  20. package/dist/check-confirm-dialog-CkaVTPT9.js +1232 -0
  21. package/dist/check-confirm-dialog-CkaVTPT9.js.map +1 -0
  22. package/dist/dialog-B_rt9S1c.js +63 -0
  23. package/dist/dialog-B_rt9S1c.js.map +1 -0
  24. package/dist/dialog-DRNUq9EA.cjs +112 -0
  25. package/dist/dialog-DRNUq9EA.cjs.map +1 -0
  26. package/dist/filter-builder-B3mZFL-K.cjs +1761 -0
  27. package/dist/filter-builder-B3mZFL-K.cjs.map +1 -0
  28. package/dist/filter-builder-DOnaXNfP.js +1605 -0
  29. package/dist/filter-builder-DOnaXNfP.js.map +1 -0
  30. package/dist/flow-runtime-B6zOHYqI.cjs +255 -0
  31. package/dist/flow-runtime-B6zOHYqI.cjs.map +1 -0
  32. package/dist/flow-runtime-D8h99FSg.js +252 -0
  33. package/dist/flow-runtime-D8h99FSg.js.map +1 -0
  34. package/dist/flow-screen-CX05b3G9.cjs +705 -0
  35. package/dist/flow-screen-CX05b3G9.cjs.map +1 -0
  36. package/dist/flow-screen-CmbARSqg.js +701 -0
  37. package/dist/flow-screen-CmbARSqg.js.map +1 -0
  38. package/dist/index.cjs +6453 -17494
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +3754 -3621
  41. package/dist/index.d.ts +3754 -3621
  42. package/dist/index.js +6217 -17219
  43. package/dist/index.js.map +1 -1
  44. package/dist/inline-flow-host-B7U1OJUe.cjs +112 -0
  45. package/dist/inline-flow-host-B7U1OJUe.cjs.map +1 -0
  46. package/dist/inline-flow-host-C3H2UpSA.js +112 -0
  47. package/dist/inline-flow-host-C3H2UpSA.js.map +1 -0
  48. package/dist/input-BI0czGyI.cjs +23 -0
  49. package/dist/input-BI0czGyI.cjs.map +1 -0
  50. package/dist/input-CUPStzZO.js +17 -0
  51. package/dist/input-CUPStzZO.js.map +1 -0
  52. package/dist/label-66W1qbke.js +29 -0
  53. package/dist/label-66W1qbke.js.map +1 -0
  54. package/dist/label-w6JYf2cF.cjs +36 -0
  55. package/dist/label-w6JYf2cF.cjs.map +1 -0
  56. package/dist/layout/theme-storage-key.cjs +5 -10
  57. package/dist/layout/theme-storage-key.cjs.map +1 -1
  58. package/dist/layout/theme-storage-key.d.cts +2 -23
  59. package/dist/layout/theme-storage-key.d.ts +2 -23
  60. package/dist/layout/theme-storage-key.js +5 -2
  61. package/dist/layout/theme-storage-key.js.map +1 -1
  62. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js +14 -0
  63. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js.map +1 -0
  64. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs +19 -0
  65. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs.map +1 -0
  66. package/dist/markdown/index.cjs +424 -511
  67. package/dist/markdown/index.cjs.map +1 -1
  68. package/dist/markdown/index.d.cts +1072 -34
  69. package/dist/markdown/index.d.ts +1072 -34
  70. package/dist/markdown/index.js +411 -459
  71. package/dist/markdown/index.js.map +1 -1
  72. package/dist/page-flow-route-CQ7HsbBm.cjs +277 -0
  73. package/dist/page-flow-route-CQ7HsbBm.cjs.map +1 -0
  74. package/dist/page-flow-route-CyXSvXSE.js +277 -0
  75. package/dist/page-flow-route-CyXSvXSE.js.map +1 -0
  76. package/dist/peek-filter-CnqHwTMU.cjs +51 -0
  77. package/dist/peek-filter-CnqHwTMU.cjs.map +1 -0
  78. package/dist/peek-filter-ErLfO153.js +28 -0
  79. package/dist/peek-filter-ErLfO153.js.map +1 -0
  80. package/dist/place-node--GdsP688.js +7315 -0
  81. package/dist/place-node--GdsP688.js.map +1 -0
  82. package/dist/place-node-SIp4XYQH.cjs +7833 -0
  83. package/dist/place-node-SIp4XYQH.cjs.map +1 -0
  84. package/dist/related-list-cards-B6x9HQQp.js +42 -0
  85. package/dist/related-list-cards-B6x9HQQp.js.map +1 -0
  86. package/dist/related-list-cards-nxuODlIA.cjs +42 -0
  87. package/dist/related-list-cards-nxuODlIA.cjs.map +1 -0
  88. package/dist/related-list-timeline-BYJKEbmA.cjs +142 -0
  89. package/dist/related-list-timeline-BYJKEbmA.cjs.map +1 -0
  90. package/dist/related-list-timeline-DEdfd-rr.js +142 -0
  91. package/dist/related-list-timeline-DEdfd-rr.js.map +1 -0
  92. package/dist/related-rows-B4levHcR.js +186 -0
  93. package/dist/related-rows-B4levHcR.js.map +1 -0
  94. package/dist/related-rows-zh5f_4QR.cjs +209 -0
  95. package/dist/related-rows-zh5f_4QR.cjs.map +1 -0
  96. package/dist/relation-create-BOaUe12G.cjs +362 -0
  97. package/dist/relation-create-BOaUe12G.cjs.map +1 -0
  98. package/dist/relation-create-Bg-eEJLs.js +337 -0
  99. package/dist/relation-create-Bg-eEJLs.js.map +1 -0
  100. package/dist/relation-many-picker-B-NCEUgX.cjs +465 -0
  101. package/dist/relation-many-picker-B-NCEUgX.cjs.map +1 -0
  102. package/dist/relation-many-picker-gdAOD-qK.js +464 -0
  103. package/dist/relation-many-picker-gdAOD-qK.js.map +1 -0
  104. package/dist/relation-picker-BAY77ivO.js +462 -0
  105. package/dist/relation-picker-BAY77ivO.js.map +1 -0
  106. package/dist/relation-picker-Da_uhFvA.cjs +479 -0
  107. package/dist/relation-picker-Da_uhFvA.cjs.map +1 -0
  108. package/dist/relation-picker-dialog-M0pQaWlp.cjs +300 -0
  109. package/dist/relation-picker-dialog-M0pQaWlp.cjs.map +1 -0
  110. package/dist/relation-picker-dialog-hA9rYrZB.js +299 -0
  111. package/dist/relation-picker-dialog-hA9rYrZB.js.map +1 -0
  112. package/dist/resource-page-router-OSQyRA5b.cjs +1779 -0
  113. package/dist/resource-page-router-OSQyRA5b.cjs.map +1 -0
  114. package/dist/resource-page-router-T4vS5gbJ.js +1582 -0
  115. package/dist/resource-page-router-T4vS5gbJ.js.map +1 -0
  116. package/dist/rich-text-DFerywgz.js +457 -0
  117. package/dist/rich-text-DFerywgz.js.map +1 -0
  118. package/dist/rich-text-vTyVjDoa.cjs +460 -0
  119. package/dist/rich-text-vTyVjDoa.cjs.map +1 -0
  120. package/dist/search-access-B7G5oR8n.cjs +812 -0
  121. package/dist/search-access-B7G5oR8n.cjs.map +1 -0
  122. package/dist/search-access-CURsWn-U.js +669 -0
  123. package/dist/search-access-CURsWn-U.js.map +1 -0
  124. package/dist/shell-BPlk8hEa.js +205 -0
  125. package/dist/shell-BPlk8hEa.js.map +1 -0
  126. package/dist/shell-DxhlsPwm.cjs +271 -0
  127. package/dist/shell-DxhlsPwm.cjs.map +1 -0
  128. package/dist/skeleton-U2noefj5.js +13 -0
  129. package/dist/skeleton-U2noefj5.js.map +1 -0
  130. package/dist/skeleton-_1gP7wan.cjs +18 -0
  131. package/dist/skeleton-_1gP7wan.cjs.map +1 -0
  132. package/dist/styles.css +255 -17
  133. package/dist/theme/theme-css.cjs +95 -96
  134. package/dist/theme/theme-css.cjs.map +1 -1
  135. package/dist/theme/theme-css.d.cts +10 -8
  136. package/dist/theme/theme-css.d.ts +10 -8
  137. package/dist/theme/theme-css.js +94 -94
  138. package/dist/theme/theme-css.js.map +1 -1
  139. package/dist/theme/theme-storage.cjs +34 -39
  140. package/dist/theme/theme-storage.cjs.map +1 -1
  141. package/dist/theme/theme-storage.d.cts +3 -1
  142. package/dist/theme/theme-storage.d.ts +3 -1
  143. package/dist/theme/theme-storage.js +33 -37
  144. package/dist/theme/theme-storage.js.map +1 -1
  145. package/dist/theme-storage-key-pYlp4aDS.d.cts +31 -0
  146. package/dist/theme-storage-key-pYlp4aDS.d.ts +31 -0
  147. package/dist/toaster-C7CiYA4Z.cjs +248 -0
  148. package/dist/toaster-C7CiYA4Z.cjs.map +1 -0
  149. package/dist/toaster-CfCRLgR-.js +164 -0
  150. package/dist/toaster-CfCRLgR-.js.map +1 -0
  151. package/dist/use-dialog-filter-state-BBCC0OEK.cjs +92 -0
  152. package/dist/use-dialog-filter-state-BBCC0OEK.cjs.map +1 -0
  153. package/dist/use-dialog-filter-state-BDcgS5Oz.js +87 -0
  154. package/dist/use-dialog-filter-state-BDcgS5Oz.js.map +1 -0
  155. package/dist/use-theme-DFlpXils.cjs +257 -0
  156. package/dist/use-theme-DFlpXils.cjs.map +1 -0
  157. package/dist/use-theme-dnE3Od3I.js +175 -0
  158. package/dist/use-theme-dnE3Od3I.js.map +1 -0
  159. package/package.json +18 -17
  160. package/dist/chunk-ABUQPOOE.js +0 -11
  161. package/dist/chunk-ABUQPOOE.js.map +0 -1
  162. package/dist/chunk-DBGGWBFG.cjs +0 -199
  163. package/dist/chunk-DBGGWBFG.cjs.map +0 -1
  164. package/dist/chunk-FMX4GLCQ.js +0 -6
  165. package/dist/chunk-FMX4GLCQ.js.map +0 -1
  166. package/dist/chunk-UC57Z243.js +0 -143
  167. package/dist/chunk-UC57Z243.js.map +0 -1
  168. package/dist/chunk-UPGPO75B.js +0 -164
  169. package/dist/chunk-UPGPO75B.js.map +0 -1
  170. package/dist/chunk-VXJAXJ3U.cjs +0 -8
  171. package/dist/chunk-VXJAXJ3U.cjs.map +0 -1
  172. package/dist/chunk-W7AEYE7A.cjs +0 -13
  173. package/dist/chunk-W7AEYE7A.cjs.map +0 -1
  174. package/dist/chunk-YF5IW525.cjs +0 -155
  175. package/dist/chunk-YF5IW525.cjs.map +0 -1
  176. package/dist/rich-text-6V7WZ3WV.cjs +0 -518
  177. package/dist/rich-text-6V7WZ3WV.cjs.map +0 -1
  178. package/dist/rich-text-N2FZEJJV.js +0 -491
  179. package/dist/rich-text-N2FZEJJV.js.map +0 -1
@@ -0,0 +1,464 @@
1
+ import { l as cn } from "./use-theme-dnE3Od3I.js";
2
+ import { f as Popover, g as PopoverTrigger, h as PopoverContent } from "./toaster-CfCRLgR-.js";
3
+ import { t as Button } from "./button-MWVeZaqj.js";
4
+ import { S as CommandList, b as CommandInput, f as useRowTitle, g as Command, n as narrowSearchDecl, t as namesHiddenField, v as CommandEmpty, x as CommandItem } from "./search-access-CURsWn-U.js";
5
+ import { t as LazyRelationPickerDialog } from "./lazy-relation-picker-dialog-2oxnA6IR.js";
6
+ import { i as selectKey, n as useSelectCreate, r as RelationViewButton, t as RelationCreateButton } from "./relation-create-Bg-eEJLs.js";
7
+ import { t as useCandidatesGate } from "./candidates-gate-CX2PBu6l.js";
8
+ import { composeListFilter, normalizeFixedFilters, relationOpenOf, resolveResourceLabel, resourceKey } from "@burojs/core";
9
+ import { useApp, useI18nCaps, useList, useListAccess, useOptionalT, useRelatedRecord, useTestIds } from "@burojs/react";
10
+ import { IconCheck, IconChevronDown, IconLoader2, IconX } from "@tabler/icons-react";
11
+ import * as React from "react";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ //#region src/fields/relation-many-picker.tsx
14
+ const DEBOUNCE_MS = 250;
15
+ function keysOf(value) {
16
+ if (!Array.isArray(value)) return [];
17
+ const seen = new Set();
18
+ const out = [];
19
+ for (const entry of value) {
20
+ const key = selectKey(entry);
21
+ if (key === null || seen.has(key)) continue;
22
+ seen.add(key);
23
+ out.push(entry);
24
+ }
25
+ return out;
26
+ }
27
+ function RelationManyPickerInput(props) {
28
+ const { name, id, value, onChange, testid, field, disabled, required, ariaInvalid, ariaDescribedBy, ariaErrorMessage, relationSeeds, fixedFilters, metaSources, resource } = props;
29
+ const meta = field?.meta;
30
+ let to;
31
+ try {
32
+ to = meta?.to === void 0 ? void 0 : resourceKey(meta.to);
33
+ } catch {
34
+ to = void 0;
35
+ }
36
+ const gate = useCandidatesGate({
37
+ to,
38
+ candidates: Array.isArray(meta?.candidates) ? meta.candidates : void 0,
39
+ sources: metaSources,
40
+ resource: resource ?? "",
41
+ fieldName: field?.name ?? name
42
+ });
43
+ if (!to) return jsx(Button, {
44
+ type: "button",
45
+ variant: "outline",
46
+ disabled: true,
47
+ "data-testid": `${testid ?? `relation-many:${name}`}:no-target`,
48
+ className: "w-full justify-between font-normal",
49
+ name,
50
+ id,
51
+ children: jsx("span", {
52
+ className: "truncate text-muted-foreground",
53
+ children: "—"
54
+ })
55
+ });
56
+ const filters = gate.filters.length === 0 ? fixedFilters : [...gate.filters, ...fixedFilters ?? []];
57
+ return jsx(ManyPicker, {
58
+ to,
59
+ keys: keysOf(value),
60
+ onChange: (next) => onChange(next),
61
+ ...typeof meta?.by === "string" ? { by: meta.by } : {},
62
+ searchField: meta?.searchField,
63
+ fixedFilters: filters,
64
+ ...gate.gate === void 0 ? {} : { gate: gate.gate },
65
+ createSeeds: relationSeeds,
66
+ name,
67
+ id,
68
+ testid: testid ?? `relation-many:${to}`,
69
+ resource: resource ?? "",
70
+ fieldName: field?.name ?? name,
71
+ open: relationOpenOf(field ?? {}),
72
+ disabled,
73
+ required,
74
+ ariaInvalid,
75
+ ariaDescribedBy,
76
+ ariaErrorMessage
77
+ });
78
+ }
79
+ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, createSeeds, name, id, testid, resource, fieldName, open: openMode, disabled, required, ariaInvalid, ariaDescribedBy, ariaErrorMessage }) {
80
+ const app = useApp();
81
+ const caps = useI18nCaps();
82
+ const t = useOptionalT();
83
+ const tid = useTestIds();
84
+ const targetDef = app.registry.get(to);
85
+ const rowTitle = useRowTitle(to, targetDef?.displayField);
86
+ const targetLabel = resolveResourceLabel(caps, to, "single", targetDef).toLowerCase();
87
+ const childPrefix = `relation-picker:${to}`;
88
+ const effectiveSearchField = searchField ?? targetDef?.displayField ?? "name";
89
+ const [open, setOpen] = React.useState(false);
90
+ const [dialogOpen, setDialogOpen] = React.useState(false);
91
+ const gated = gate !== void 0;
92
+ const [dialogMounted, setDialogMounted] = React.useState(false);
93
+ React.useEffect(() => {
94
+ if (gated) setDialogOpen(false);
95
+ }, [gated]);
96
+ React.useEffect(() => {
97
+ if (dialogOpen) setDialogMounted(true);
98
+ }, [dialogOpen]);
99
+ const [search, setSearch] = React.useState("");
100
+ const [debouncedSearch, setDebouncedSearch] = React.useState("");
101
+ const triggerRef = React.useRef(null);
102
+ const focusTrigger = React.useCallback(() => triggerRef.current?.focus(), []);
103
+ React.useEffect(() => {
104
+ const handle = setTimeout(() => setDebouncedSearch(search), DEBOUNCE_MS);
105
+ return () => clearTimeout(handle);
106
+ }, [search]);
107
+ React.useEffect(() => {
108
+ if (!open) {
109
+ setSearch("");
110
+ setDebouncedSearch("");
111
+ }
112
+ }, [open]);
113
+ const targetIdField = (targetDef?.data)?.idField;
114
+ const idField = typeof targetIdField === "string" && targetIdField.length > 0 ? targetIdField : "id";
115
+ const valueField = by ?? idField;
116
+ const access = useListAccess(to);
117
+ const accessReady = access.status === "ready";
118
+ const searchDecl = React.useMemo(() => narrowSearchDecl(targetDef?.views?.list?.search ?? { fields: [effectiveSearchField] }, (field) => access.fieldVisible(field)), [
119
+ targetDef,
120
+ effectiveSearchField,
121
+ access
122
+ ]);
123
+ const filter = React.useMemo(() => composeListFilter({
124
+ fixed: normalizeFixedFilters(fixedFilters),
125
+ search: debouncedSearch,
126
+ searchDecl,
127
+ searchContext: {
128
+ resource: to,
129
+ def: targetDef
130
+ }
131
+ }), [
132
+ fixedFilters,
133
+ debouncedSearch,
134
+ searchDecl,
135
+ to,
136
+ targetDef
137
+ ]);
138
+ const blocked = !accessReady || gate !== void 0 || namesHiddenField(filter, (field) => access.fieldVisible(field));
139
+ const append = React.useCallback((next) => {
140
+ if (keys.some((key) => String(key) === String(next))) return;
141
+ onChange([...keys, next]);
142
+ }, [keys, onChange]);
143
+ const toggle = React.useCallback((next) => {
144
+ onChange(keys.some((key) => String(key) === String(next)) ? keys.filter((key) => String(key) !== String(next)) : [...keys, next]);
145
+ }, [keys, onChange]);
146
+ const { create, reader: createReader } = useSelectCreate({
147
+ to,
148
+ by,
149
+ valueField,
150
+ idField,
151
+ fixedFilters,
152
+ held: gated,
153
+ searchDecl,
154
+ searchContext: {
155
+ resource: to,
156
+ def: targetDef
157
+ },
158
+ onValue: (picked) => {
159
+ append(picked);
160
+ setDialogOpen(false);
161
+ }
162
+ });
163
+ const listQuery = useList(to, {
164
+ ...filter ? { filter } : {},
165
+ pagination: {
166
+ kind: "offset",
167
+ page: 1,
168
+ pageSize: 10
169
+ },
170
+ ...fixedFilters && fixedFilters.length > 0 ? { providerOptionsOverride: { ignoreScope: true } } : {},
171
+ enabled: open && targetDef != null && !blocked
172
+ });
173
+ const rows = listQuery.data?.data ?? [];
174
+ const isLoading = listQuery.isLoading || listQuery.isFetching;
175
+ const error = listQuery.isError ? listQuery.error : null;
176
+ if (!targetDef) return jsx(Button, {
177
+ type: "button",
178
+ variant: "outline",
179
+ disabled: true,
180
+ "data-testid": `${childPrefix}:unregistered`,
181
+ className: "w-full justify-between font-normal",
182
+ name,
183
+ id,
184
+ children: jsx("span", {
185
+ className: "truncate text-muted-foreground",
186
+ children: keys.map(String).join(", ") || "—"
187
+ })
188
+ });
189
+ const placeholder = t("relation.select", {
190
+ params: { name: targetLabel },
191
+ defaultValue: `Select ${targetLabel}…`
192
+ });
193
+ const chips = jsx("div", {
194
+ className: "flex flex-wrap items-center gap-1",
195
+ "data-testid": `${testid}:chips`,
196
+ children: keys.map((key) => jsx(Chip, {
197
+ to,
198
+ keyValue: key,
199
+ by,
200
+ resource,
201
+ fieldName,
202
+ open: openMode,
203
+ testId: tid.relationManyChip(resource, fieldName, String(key)),
204
+ disabled: disabled === true,
205
+ ignoreScope: fixedFilters !== void 0 && fixedFilters.length > 0,
206
+ focusTrigger,
207
+ onRemove: () => onChange(keys.filter((entry) => String(entry) !== String(key)))
208
+ }, String(key)))
209
+ });
210
+ if (gate?.kind === "pending") return jsxs("div", {
211
+ className: "flex flex-col gap-1.5",
212
+ children: [chips, jsxs(Button, {
213
+ type: "button",
214
+ variant: "outline",
215
+ disabled: true,
216
+ "aria-busy": "true",
217
+ "data-testid": testid,
218
+ "data-buro-candidates": "pending",
219
+ className: "w-full justify-between font-normal",
220
+ children: [jsx("span", {
221
+ className: "truncate text-muted-foreground",
222
+ children: placeholder
223
+ }), jsx(IconLoader2, {
224
+ className: "h-4 w-4 shrink-0 animate-spin opacity-50",
225
+ "aria-hidden": true
226
+ })]
227
+ })]
228
+ });
229
+ if (gate?.kind === "forbidden") return jsxs("div", {
230
+ className: "flex flex-col gap-1.5",
231
+ children: [
232
+ chips,
233
+ jsxs(Button, {
234
+ type: "button",
235
+ variant: "outline",
236
+ disabled: true,
237
+ "data-testid": testid,
238
+ "data-buro-candidates": "forbidden",
239
+ "aria-describedby": gate.testId,
240
+ className: "w-full justify-between font-normal",
241
+ children: [jsx("span", {
242
+ className: "truncate text-muted-foreground",
243
+ children: placeholder
244
+ }), jsx(IconChevronDown, {
245
+ className: "h-4 w-4 shrink-0 opacity-50",
246
+ "aria-hidden": true
247
+ })]
248
+ }),
249
+ jsx("p", {
250
+ id: gate.testId,
251
+ "data-testid": gate.testId,
252
+ className: "text-xs text-muted-foreground",
253
+ children: gate.message
254
+ })
255
+ ]
256
+ });
257
+ return jsxs(Popover, {
258
+ open,
259
+ onOpenChange: setOpen,
260
+ children: [
261
+ jsxs("div", {
262
+ className: "flex flex-col gap-1.5",
263
+ children: [chips, jsx(PopoverTrigger, {
264
+ asChild: true,
265
+ children: jsxs(Button, {
266
+ ref: triggerRef,
267
+ type: "button",
268
+ variant: "outline",
269
+ role: "combobox",
270
+ "aria-expanded": open,
271
+ "aria-haspopup": "listbox",
272
+ "aria-required": required,
273
+ "aria-invalid": ariaInvalid || void 0,
274
+ "aria-describedby": ariaDescribedBy,
275
+ ...ariaErrorMessage ? { "aria-errormessage": ariaErrorMessage } : {},
276
+ disabled,
277
+ "data-testid": testid,
278
+ name,
279
+ id,
280
+ className: "w-full justify-between font-normal",
281
+ children: [jsx("span", {
282
+ className: "truncate text-left text-muted-foreground",
283
+ children: placeholder
284
+ }), jsx(IconChevronDown, {
285
+ className: "h-4 w-4 shrink-0 opacity-50",
286
+ "aria-hidden": true
287
+ })]
288
+ })
289
+ })]
290
+ }),
291
+ jsx(PopoverContent, {
292
+ className: "w-[var(--radix-popover-trigger-width)] p-0",
293
+ align: "start",
294
+ children: gate?.kind === "empty" ? jsx("p", {
295
+ className: "px-3 py-6 text-center text-sm text-muted-foreground",
296
+ "data-testid": gate.testId,
297
+ children: gate.message
298
+ }) : jsxs(Command, {
299
+ shouldFilter: false,
300
+ children: [
301
+ searchDecl !== void 0 && jsx(CommandInput, {
302
+ value: search,
303
+ onValueChange: setSearch,
304
+ placeholder: t("toolbar.search", {
305
+ params: { name: targetLabel },
306
+ defaultValue: `Search ${targetLabel}…`
307
+ }),
308
+ "data-testid": `${childPrefix}:search`,
309
+ "aria-label": t("toolbar.search", {
310
+ params: { name: targetLabel },
311
+ defaultValue: `Search ${targetLabel}`
312
+ }),
313
+ onKeyDown: (event) => {
314
+ if (event.key === "Backspace" && search === "" && keys.length > 0) {
315
+ event.preventDefault();
316
+ onChange(keys.slice(0, -1));
317
+ }
318
+ if (event.key === " " && search === "") {
319
+ event.preventDefault();
320
+ document.querySelector(`[data-testid="${childPrefix}:list"] [aria-selected="true"]`)?.click();
321
+ }
322
+ }
323
+ }),
324
+ jsxs(CommandList, {
325
+ "data-testid": `${childPrefix}:list`,
326
+ "aria-multiselectable": "true",
327
+ "aria-label": targetLabel,
328
+ children: [
329
+ isLoading && jsxs("div", {
330
+ className: "flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground",
331
+ "data-testid": `${childPrefix}:loading`,
332
+ children: [jsx(IconLoader2, {
333
+ className: "h-4 w-4 animate-spin",
334
+ "aria-hidden": true
335
+ }), jsx("span", { children: t("table.loading", { defaultValue: "Loading…" }) })]
336
+ }),
337
+ !isLoading && error && jsx("div", {
338
+ className: "px-3 py-6 text-center text-sm text-muted-foreground",
339
+ "data-testid": `${childPrefix}:error`,
340
+ title: error.message,
341
+ children: t("relation.failed", { defaultValue: "Failed to load options." })
342
+ }),
343
+ !isLoading && !error && rows.length === 0 && jsx(CommandEmpty, {
344
+ "data-testid": `${childPrefix}:empty`,
345
+ children: t("relation.noResults", { defaultValue: "No results." })
346
+ }),
347
+ !isLoading && !error && rows.map((row) => {
348
+ const rowValue = row[valueField];
349
+ if (rowValue === null || rowValue === void 0 || rowValue === "") return null;
350
+ const idStr = String(rowValue);
351
+ const label = rowTitle(row);
352
+ const chosen = keys.some((key) => String(key) === idStr);
353
+ return jsxs(CommandItem, {
354
+ value: `${label} ${idStr}`,
355
+ "data-testid": `${childPrefix}:option:${idStr}`,
356
+ "aria-checked": chosen,
357
+ onSelect: () => toggle(rowValue),
358
+ children: [
359
+ jsx(IconCheck, {
360
+ className: cn("mr-2 h-4 w-4 shrink-0", chosen ? "opacity-100" : "opacity-0"),
361
+ "aria-hidden": true
362
+ }),
363
+ jsx("span", {
364
+ className: "truncate",
365
+ children: label
366
+ }),
367
+ label === idStr ? null : jsxs("span", {
368
+ className: "ml-auto shrink-0 pl-2 text-xs text-muted-foreground",
369
+ children: ["#", idStr]
370
+ })
371
+ ]
372
+ }, idStr);
373
+ })
374
+ ]
375
+ }),
376
+ jsxs("div", {
377
+ className: "flex items-center justify-between gap-2 border-t px-3 py-2 text-sm",
378
+ children: [jsx("button", {
379
+ type: "button",
380
+ onClick: () => {
381
+ setOpen(false);
382
+ setDialogOpen(true);
383
+ },
384
+ "data-testid": `${childPrefix}:advanced`,
385
+ className: "text-foreground hover:underline focus:outline-hidden focus-visible:underline",
386
+ children: t("relationPicker.advancedSearch", { defaultValue: "Advanced search" })
387
+ }), !disabled && jsx(RelationCreateButton, {
388
+ to,
389
+ fieldName,
390
+ create,
391
+ seeds: createSeeds,
392
+ search,
393
+ beforeOpen: () => {
394
+ setOpen(false);
395
+ focusTrigger();
396
+ },
397
+ className: "text-foreground hover:underline focus:outline-hidden focus-visible:underline disabled:opacity-50"
398
+ })]
399
+ })
400
+ ]
401
+ })
402
+ }),
403
+ dialogMounted && !gated && jsx(LazyRelationPickerDialog, {
404
+ to,
405
+ open: dialogOpen,
406
+ onOpenChange: setDialogOpen,
407
+ onSelect: () => void 0,
408
+ multiple: true,
409
+ selected: keys,
410
+ onToggle: toggle,
411
+ valueField,
412
+ searchField,
413
+ fixedFilters,
414
+ ...disabled ? {} : { create },
415
+ createSeeds,
416
+ fieldName,
417
+ onBeforeCreate: focusTrigger
418
+ }),
419
+ createReader
420
+ ]
421
+ });
422
+ }
423
+ function Chip({ to, keyValue, by, resource, fieldName, testId, open: openMode, disabled, ignoreScope, focusTrigger, onRemove }) {
424
+ const t = useOptionalT();
425
+ const targetDef = useApp().registry.get(to);
426
+ const rowTitle = useRowTitle(to, targetDef?.displayField);
427
+ const { record, isLoading } = useRelatedRecord(to, keyValue, by === void 0 ? void 0 : { by });
428
+ const title = record ? rowTitle(record) : String(keyValue);
429
+ return jsxs("span", {
430
+ "data-testid": testId,
431
+ "data-resource": resource,
432
+ className: "inline-flex items-center gap-0.5 rounded-md border bg-secondary pl-2 text-sm text-secondary-foreground",
433
+ children: [
434
+ jsx("span", {
435
+ className: "max-w-[16rem] truncate",
436
+ children: isLoading ? t("table.loading", { defaultValue: "Loading…" }) : title
437
+ }),
438
+ jsx(RelationViewButton, {
439
+ to,
440
+ value: keyValue,
441
+ by,
442
+ ignoreScope,
443
+ fieldName: `${fieldName}:${String(keyValue)}`,
444
+ beforeOpen: focusTrigger,
445
+ open: openMode
446
+ }),
447
+ !disabled && jsx("button", {
448
+ type: "button",
449
+ onClick: onRemove,
450
+ "data-testid": `${testId}:remove`,
451
+ "aria-label": `${t("relationPicker.clear", { defaultValue: "Clear" })} ${title}`,
452
+ className: "inline-flex size-6 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
453
+ children: jsx(IconX, {
454
+ className: "size-3.5",
455
+ "aria-hidden": true
456
+ })
457
+ })
458
+ ]
459
+ });
460
+ }
461
+ //#endregion
462
+ export { RelationManyPickerInput };
463
+
464
+ //# sourceMappingURL=relation-many-picker-gdAOD-qK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relation-many-picker-gdAOD-qK.js","names":[],"sources":["../src/fields/relation-many-picker.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ErasedResourceHandle,\n type FilterExpression,\n type FixedFilterInput,\n type RelationCandidate,\n type RelationOpen,\n relationOpenOf,\n type ResourceRefInput,\n type SearchDecl,\n composeListFilter,\n normalizeFixedFilters,\n resolveResourceLabel,\n resourceKey,\n} from '@burojs/core';\nimport {\n useApp,\n useI18nCaps,\n useList,\n useListAccess,\n useOptionalT,\n useRelatedRecord,\n useTestIds,\n} from '@burojs/react';\nimport { IconCheck, IconChevronDown, IconLoader2, IconX } from '@tabler/icons-react';\nimport * as React from 'react';\nimport { cn } from '../lib/cn.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 { useRowTitle } from '../resource/use-row-title.js';\nimport { useCandidatesGate } from './candidates-gate.js';\nimport { RelationCreateButton, useSelectCreate } from './relation-create.js';\nimport { LazyRelationPickerDialog } from './lazy-relation-picker-dialog.js';\nimport { RelationViewButton, selectKey } from './relation-view-button.js';\nimport type { FieldInputProps } from './registry.js';\nimport { namesHiddenField, narrowSearchDecl } from './search-access.js';\n\ntype ErasedExpression = FilterExpression<ErasedResourceHandle>;\ntype Key = string | number;\n\nconst DEBOUNCE_MS = 250;\n\n/** The keys a to-many field holds: strings and finite numbers, the user's order, no repeats. */\nfunction keysOf(value: unknown): readonly Key[] {\n if (!Array.isArray(value)) return [];\n const seen = new Set<string>();\n const out: Key[] = [];\n for (const entry of value) {\n const key = selectKey(entry);\n if (key === null || seen.has(key)) continue;\n seen.add(key);\n out.push(entry as Key);\n }\n return out;\n}\n\n/**\n * Form input of `f.relationMany` (P7 3.5): the chosen rows as chips (titles through the relation\n * aggregator, one batched read), a popover that searches the same option query a single select\n * does (candidates, field access and the create hand-back included), \"Advanced search\" in a\n * multiple-choice dialog, a \"view\" on every chip and \"+ create\" that appends what it created.\n * The value is `row[by ?? id]` for each chosen row. Loaded lazily: it is not on any screen's\n * first paint.\n */\nexport function RelationManyPickerInput(props: FieldInputProps): React.ReactElement {\n const {\n name,\n id,\n value,\n onChange,\n testid,\n field,\n disabled,\n required,\n ariaInvalid,\n ariaDescribedBy,\n ariaErrorMessage,\n relationSeeds,\n fixedFilters,\n metaSources,\n resource,\n } = props;\n const meta = field?.meta as\n | { to?: ResourceRefInput; by?: unknown; searchField?: string; candidates?: unknown }\n | undefined;\n let to: string | undefined;\n try {\n to = meta?.to === undefined ? undefined : resourceKey(meta.to);\n } catch {\n to = undefined;\n }\n const gate = useCandidatesGate({\n to,\n candidates: Array.isArray(meta?.candidates)\n ? (meta.candidates as readonly RelationCandidate[])\n : undefined,\n sources: metaSources,\n resource: resource ?? '',\n fieldName: field?.name ?? name,\n });\n if (!to) {\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={`${testid ?? `relation-many:${name}`}:no-target`}\n className=\"w-full justify-between font-normal\"\n name={name}\n id={id}\n >\n <span className=\"truncate text-muted-foreground\">—</span>\n </Button>\n );\n }\n const filters =\n gate.filters.length === 0 ? fixedFilters : [...gate.filters, ...(fixedFilters ?? [])];\n return (\n <ManyPicker\n to={to}\n keys={keysOf(value)}\n onChange={(next) => onChange(next)}\n {...(typeof meta?.by === 'string' ? { by: meta.by } : {})}\n searchField={meta?.searchField}\n fixedFilters={filters}\n {...(gate.gate === undefined ? {} : { gate: gate.gate })}\n createSeeds={relationSeeds}\n name={name}\n id={id}\n testid={testid ?? `relation-many:${to}`}\n resource={resource ?? ''}\n fieldName={field?.name ?? name}\n open={relationOpenOf(field ?? {})}\n disabled={disabled}\n required={required}\n ariaInvalid={ariaInvalid}\n ariaDescribedBy={ariaDescribedBy}\n ariaErrorMessage={ariaErrorMessage}\n />\n );\n}\n\ninterface ManyPickerProps {\n readonly to: string;\n readonly keys: readonly Key[];\n readonly onChange: (next: readonly Key[]) => void;\n readonly by?: string;\n readonly searchField?: string | undefined;\n readonly fixedFilters: readonly FixedFilterInput[] | undefined;\n readonly gate?: import('./candidates-gate.js').CandidatesGate;\n readonly createSeeds: Readonly<Record<string, unknown>> | undefined;\n readonly name: string;\n readonly id: string | undefined;\n readonly testid: string;\n readonly resource: string;\n readonly fieldName: string;\n readonly open: RelationOpen;\n readonly disabled: boolean | undefined;\n readonly required: boolean | undefined;\n readonly ariaInvalid: boolean | undefined;\n readonly ariaDescribedBy: string | undefined;\n readonly ariaErrorMessage: string | undefined;\n}\n\nfunction ManyPicker({\n to,\n keys,\n onChange,\n by,\n searchField,\n fixedFilters,\n gate,\n createSeeds,\n name,\n id,\n testid,\n resource,\n fieldName,\n open: openMode,\n disabled,\n required,\n ariaInvalid,\n ariaDescribedBy,\n ariaErrorMessage,\n}: ManyPickerProps): React.ReactElement {\n const app = useApp();\n const caps = useI18nCaps();\n const t = useOptionalT();\n const tid = useTestIds();\n const targetDef = app.registry.get(to);\n const rowTitle = useRowTitle(to, targetDef?.displayField);\n const targetLabel = resolveResourceLabel(caps, to, 'single', targetDef).toLowerCase();\n const childPrefix = `relation-picker:${to}`;\n const effectiveSearchField = searchField ?? targetDef?.displayField ?? 'name';\n const [open, setOpen] = React.useState(false);\n const [dialogOpen, setDialogOpen] = React.useState(false);\n // Mounted on the first open, and only while no candidates gate holds the read: without the\n // candidate filters the dialog would list rows the field must not offer.\n const gated = gate !== 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 const triggerRef = React.useRef<HTMLButtonElement | null>(null);\n const focusTrigger = React.useCallback(() => triggerRef.current?.focus(), []);\n\n React.useEffect(() => {\n const handle = setTimeout(() => setDebouncedSearch(search), DEBOUNCE_MS);\n return () => clearTimeout(handle);\n }, [search]);\n React.useEffect(() => {\n if (!open) {\n setSearch('');\n setDebouncedSearch('');\n }\n }, [open]);\n\n const targetIdField = (targetDef?.data as { readonly idField?: unknown } | undefined)?.idField;\n const idField =\n typeof targetIdField === 'string' && targetIdField.length > 0 ? targetIdField : 'id';\n const valueField = by ?? idField;\n const access = useListAccess(to);\n const accessReady = access.status === 'ready';\n const searchDecl = React.useMemo<SearchDecl | undefined>(\n () =>\n narrowSearchDecl(\n targetDef?.views?.list?.search ?? { fields: [effectiveSearchField] },\n (field) => access.fieldVisible(field),\n ),\n [targetDef, effectiveSearchField, access],\n );\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 const blocked =\n !accessReady ||\n gate !== undefined ||\n namesHiddenField(filter, (field) => access.fieldVisible(field));\n\n const append = React.useCallback(\n (next: Key) => {\n if (keys.some((key) => String(key) === String(next))) return;\n onChange([...keys, next]);\n },\n [keys, onChange],\n );\n const toggle = React.useCallback(\n (next: Key) => {\n onChange(\n keys.some((key) => String(key) === String(next))\n ? keys.filter((key) => String(key) !== String(next))\n : [...keys, next],\n );\n },\n [keys, onChange],\n );\n const { create, reader: createReader } = useSelectCreate({\n to,\n by,\n valueField,\n idField,\n fixedFilters,\n held: gated,\n searchDecl,\n searchContext: { resource: to, def: targetDef },\n // What was created and fits is appended, as a pick would be; the dialog it came from is done.\n onValue: (picked) => {\n append(picked);\n setDialogOpen(false);\n },\n });\n\n const listQuery = useList(to, {\n ...(filter ? { filter } : {}),\n pagination: { kind: 'offset', page: 1, pageSize: 10 },\n ...(fixedFilters && fixedFilters.length > 0\n ? { providerOptionsOverride: { ignoreScope: true } }\n : {}),\n enabled: open && targetDef != null && !blocked,\n });\n const rows = (listQuery.data?.data ?? []) as Array<Record<string, unknown>>;\n const isLoading = listQuery.isLoading || listQuery.isFetching;\n const error = listQuery.isError ? (listQuery.error as unknown as Error) : null;\n\n if (!targetDef) {\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={`${childPrefix}:unregistered`}\n className=\"w-full justify-between font-normal\"\n name={name}\n id={id}\n >\n <span className=\"truncate text-muted-foreground\">{keys.map(String).join(', ') || '—'}</span>\n </Button>\n );\n }\n\n const placeholder = t('relation.select', {\n params: { name: targetLabel },\n defaultValue: `Select ${targetLabel}…`,\n });\n const chips = (\n <div className=\"flex flex-wrap items-center gap-1\" data-testid={`${testid}:chips`}>\n {keys.map((key) => (\n <Chip\n key={String(key)}\n to={to}\n keyValue={key}\n by={by}\n resource={resource}\n fieldName={fieldName}\n open={openMode}\n testId={tid.relationManyChip(resource, fieldName, String(key))}\n disabled={disabled === true}\n ignoreScope={fixedFilters !== undefined && fixedFilters.length > 0}\n focusTrigger={focusTrigger}\n onRemove={() => onChange(keys.filter((entry) => String(entry) !== String(key)))}\n />\n ))}\n </div>\n );\n\n if (gate?.kind === 'pending')\n return (\n <div className=\"flex flex-col gap-1.5\">\n {chips}\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n aria-busy=\"true\"\n data-testid={testid}\n data-buro-candidates=\"pending\"\n className=\"w-full justify-between font-normal\"\n >\n <span className=\"truncate text-muted-foreground\">{placeholder}</span>\n <IconLoader2 className=\"h-4 w-4 shrink-0 animate-spin opacity-50\" aria-hidden />\n </Button>\n </div>\n );\n if (gate?.kind === 'forbidden')\n return (\n <div className=\"flex flex-col gap-1.5\">\n {chips}\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={testid}\n data-buro-candidates=\"forbidden\"\n aria-describedby={gate.testId}\n className=\"w-full justify-between font-normal\"\n >\n <span className=\"truncate text-muted-foreground\">{placeholder}</span>\n <IconChevronDown className=\"h-4 w-4 shrink-0 opacity-50\" aria-hidden />\n </Button>\n <p id={gate.testId} data-testid={gate.testId} className=\"text-xs text-muted-foreground\">\n {gate.message}\n </p>\n </div>\n );\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <div className=\"flex flex-col gap-1.5\">\n {chips}\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={testid}\n name={name}\n id={id}\n className=\"w-full justify-between font-normal\"\n >\n <span className=\"truncate text-left text-muted-foreground\">{placeholder}</span>\n <IconChevronDown className=\"h-4 w-4 shrink-0 opacity-50\" aria-hidden />\n </Button>\n </PopoverTrigger>\n </div>\n <PopoverContent className=\"w-[var(--radix-popover-trigger-width)] p-0\" align=\"start\">\n {gate?.kind === 'empty' ? (\n <p\n className=\"px-3 py-6 text-center text-sm text-muted-foreground\"\n data-testid={gate.testId}\n >\n {gate.message}\n </p>\n ) : (\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 onKeyDown={(event) => {\n // Backspace in an empty search takes the last chip off.\n if (event.key === 'Backspace' && search === '' && keys.length > 0) {\n event.preventDefault();\n onChange(keys.slice(0, -1));\n }\n // Space toggles the highlighted option while nothing is typed.\n if (event.key === ' ' && search === '') {\n event.preventDefault();\n document\n .querySelector<HTMLElement>(\n `[data-testid=\"${childPrefix}:list\"] [aria-selected=\"true\"]`,\n )\n ?.click();\n }\n }}\n />\n )}\n <CommandList\n data-testid={`${childPrefix}:list`}\n aria-multiselectable=\"true\"\n aria-label={targetLabel}\n >\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 <div\n className=\"px-3 py-6 text-center text-sm text-muted-foreground\"\n data-testid={`${childPrefix}:error`}\n title={error.message}\n >\n {t('relation.failed', { defaultValue: 'Failed to load options.' })}\n </div>\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 rowValue = row[valueField];\n if (rowValue === null || rowValue === undefined || rowValue === '') return null;\n const idStr = String(rowValue);\n const label = rowTitle(row);\n const chosen = keys.some((key) => String(key) === idStr);\n return (\n <CommandItem\n key={idStr}\n value={`${label} ${idStr}`}\n data-testid={`${childPrefix}:option:${idStr}`}\n aria-checked={chosen}\n onSelect={() => toggle(rowValue as Key)}\n >\n <IconCheck\n className={cn(\n 'mr-2 h-4 w-4 shrink-0',\n chosen ? 'opacity-100' : 'opacity-0',\n )}\n aria-hidden\n />\n <span className=\"truncate\">{label}</span>\n {/* A label that already is the id would print it twice (REF-40). */}\n {label === idStr ? null : (\n <span className=\"ml-auto shrink-0 pl-2 text-xs text-muted-foreground\">\n #{idStr}\n </span>\n )}\n </CommandItem>\n );\n })}\n </CommandList>\n <div className=\"flex items-center justify-between gap-2 border-t px-3 py-2 text-sm\">\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}\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 </div>\n </Command>\n )}\n </PopoverContent>\n {dialogMounted && !gated && (\n <LazyRelationPickerDialog\n to={to}\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n onSelect={() => undefined}\n multiple\n selected={keys}\n onToggle={toggle}\n valueField={valueField}\n searchField={searchField}\n fixedFilters={fixedFilters}\n {...(disabled ? {} : { create })}\n createSeeds={createSeeds}\n fieldName={fieldName}\n onBeforeCreate={focusTrigger}\n />\n )}\n {createReader}\n </Popover>\n );\n}\n\n/** One chosen row: its title (the aggregator batches every chip's read), \"view\" and remove. */\nfunction Chip({\n to,\n keyValue,\n by,\n resource,\n fieldName,\n testId,\n open: openMode,\n disabled,\n ignoreScope,\n focusTrigger,\n onRemove,\n}: {\n readonly to: string;\n readonly keyValue: Key;\n readonly by: string | undefined;\n readonly resource: string;\n readonly fieldName: string;\n readonly testId: string;\n readonly open: RelationOpen;\n readonly disabled: boolean;\n readonly ignoreScope: boolean;\n readonly focusTrigger: () => void;\n readonly onRemove: () => void;\n}): React.ReactElement {\n const t = useOptionalT();\n const app = useApp();\n const targetDef = app.registry.get(to);\n const rowTitle = useRowTitle(to, targetDef?.displayField);\n const { record, isLoading } = useRelatedRecord(\n to,\n keyValue,\n by === undefined ? undefined : { by },\n );\n const title = record ? rowTitle(record as Record<string, unknown>) : String(keyValue);\n return (\n <span\n data-testid={testId}\n data-resource={resource}\n className=\"inline-flex items-center gap-0.5 rounded-md border bg-secondary pl-2 text-sm text-secondary-foreground\"\n >\n <span className=\"max-w-[16rem] truncate\">\n {isLoading ? t('table.loading', { defaultValue: 'Loading…' }) : title}\n </span>\n <RelationViewButton\n to={to}\n value={keyValue}\n by={by}\n ignoreScope={ignoreScope}\n fieldName={`${fieldName}:${String(keyValue)}`}\n beforeOpen={focusTrigger}\n open={openMode}\n />\n {!disabled && (\n <button\n type=\"button\"\n onClick={onRemove}\n data-testid={`${testId}:remove`}\n aria-label={`${t('relationPicker.clear', { defaultValue: 'Clear' })} ${title}`}\n className=\"inline-flex size-6 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <IconX className=\"size-3.5\" aria-hidden />\n </button>\n )}\n </span>\n );\n}\n"],"mappings":"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"}