@burojs/ui-shadcn 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{bulk-summary-CrEij5vq.cjs → bulk-summary-2HLgz7Il.cjs} +31 -5
- package/dist/bulk-summary-2HLgz7Il.cjs.map +1 -0
- package/dist/{bulk-summary-BWSAS7k2.js → bulk-summary-D6A0ua21.js} +32 -6
- package/dist/bulk-summary-D6A0ua21.js.map +1 -0
- package/dist/{candidates-relation-picker-CS_JZ4Fd.js → candidates-relation-picker-QvoY2bOQ.js} +3 -3
- package/dist/{candidates-relation-picker-CS_JZ4Fd.js.map → candidates-relation-picker-QvoY2bOQ.js.map} +1 -1
- package/dist/{candidates-relation-picker-y5M6_oe4.cjs → candidates-relation-picker-pEAWKQEY.cjs} +3 -3
- package/dist/{candidates-relation-picker-y5M6_oe4.cjs.map → candidates-relation-picker-pEAWKQEY.cjs.map} +1 -1
- package/dist/{check-confirm-dialog-2j0gOd7x.js → check-confirm-dialog-B-CbJTln.js} +68 -8
- package/dist/{check-confirm-dialog-2j0gOd7x.js.map → check-confirm-dialog-B-CbJTln.js.map} +1 -1
- package/dist/{check-confirm-dialog-C-NHgMST.cjs → check-confirm-dialog-W-8WOmGh.cjs} +72 -6
- package/dist/check-confirm-dialog-W-8WOmGh.cjs.map +1 -0
- package/dist/{filter-builder-DaWHUZuV.js → filter-builder-04nas3eq.js} +127 -61
- package/dist/filter-builder-04nas3eq.js.map +1 -0
- package/dist/{filter-builder-DFu0JP-1.cjs → filter-builder-DQX-TMFh.cjs} +140 -68
- package/dist/filter-builder-DQX-TMFh.cjs.map +1 -0
- package/dist/{flow-runtime-DyqTmK3H.js → flow-runtime-BEIyym7d.js} +7 -4
- package/dist/{flow-runtime-DyqTmK3H.js.map → flow-runtime-BEIyym7d.js.map} +1 -1
- package/dist/{flow-runtime-BpmrIUb6.cjs → flow-runtime-DyNIYfUP.cjs} +7 -4
- package/dist/{flow-runtime-BpmrIUb6.cjs.map → flow-runtime-DyNIYfUP.cjs.map} +1 -1
- package/dist/{flow-screen-DCdli6Ha.js → flow-screen-DY1Z9PbG.js} +56 -15
- package/dist/flow-screen-DY1Z9PbG.js.map +1 -0
- package/dist/{flow-screen-Bcz9NF2V.cjs → flow-screen-FE5Xhb3t.cjs} +54 -13
- package/dist/flow-screen-FE5Xhb3t.cjs.map +1 -0
- package/dist/index.cjs +564 -170
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +235 -95
- package/dist/index.d.ts +235 -95
- package/dist/index.js +562 -169
- package/dist/index.js.map +1 -1
- package/dist/{inline-flow-host-C0rI9B7f.js → inline-flow-host-D6u-RlsV.js} +3 -3
- package/dist/{inline-flow-host-C0rI9B7f.js.map → inline-flow-host-D6u-RlsV.js.map} +1 -1
- package/dist/{inline-flow-host-3vw7LzzD.cjs → inline-flow-host-yVLo6-sM.cjs} +3 -3
- package/dist/{inline-flow-host-3vw7LzzD.cjs.map → inline-flow-host-yVLo6-sM.cjs.map} +1 -1
- package/dist/{lazy-relation-picker-dialog-CAScLPb2.js → lazy-relation-picker-dialog-BaORufxQ.js} +2 -2
- package/dist/{lazy-relation-picker-dialog-CAScLPb2.js.map → lazy-relation-picker-dialog-BaORufxQ.js.map} +1 -1
- package/dist/{lazy-relation-picker-dialog-Bt8lrEIu.cjs → lazy-relation-picker-dialog-C_VlGlDz.cjs} +2 -2
- package/dist/{lazy-relation-picker-dialog-Bt8lrEIu.cjs.map → lazy-relation-picker-dialog-C_VlGlDz.cjs.map} +1 -1
- package/dist/{page-flow-route-BHUEpn0I.js → page-flow-route-CyMzUidK.js} +8 -9
- package/dist/page-flow-route-CyMzUidK.js.map +1 -0
- package/dist/{page-flow-route-DS8IVTVH.cjs → page-flow-route-OFx7h7Ja.cjs} +8 -9
- package/dist/page-flow-route-OFx7h7Ja.cjs.map +1 -0
- package/dist/{place-node-CNb3w-c7.cjs → place-node-BZF5Af6x.cjs} +224 -151
- package/dist/place-node-BZF5Af6x.cjs.map +1 -0
- package/dist/{place-node-CfzQvh4Y.js → place-node-DRjmJGQ_.js} +210 -125
- package/dist/place-node-DRjmJGQ_.js.map +1 -0
- package/dist/{related-list-cards-BIqqY69Y.js → related-list-cards-2hLitVkg.js} +3 -3
- package/dist/{related-list-cards-BIqqY69Y.js.map → related-list-cards-2hLitVkg.js.map} +1 -1
- package/dist/{related-list-cards-CelMxl4q.cjs → related-list-cards-xVvgSlu-.cjs} +3 -3
- package/dist/{related-list-cards-CelMxl4q.cjs.map → related-list-cards-xVvgSlu-.cjs.map} +1 -1
- package/dist/related-list-timeline-CxJqe6eK.js +167 -0
- package/dist/related-list-timeline-CxJqe6eK.js.map +1 -0
- package/dist/related-list-timeline-Dsf5WewG.cjs +167 -0
- package/dist/related-list-timeline-Dsf5WewG.cjs.map +1 -0
- package/dist/{related-rows-MPnLCmPF.cjs → related-rows-DDhp9axl.cjs} +4 -4
- package/dist/{related-rows-MPnLCmPF.cjs.map → related-rows-DDhp9axl.cjs.map} +1 -1
- package/dist/{related-rows--uP2iKQC.js → related-rows-DL6CWsrj.js} +4 -4
- package/dist/{related-rows--uP2iKQC.js.map → related-rows-DL6CWsrj.js.map} +1 -1
- package/dist/{relation-create-C43cdPQt.js → relation-create-6WicROWA.js} +33 -5
- package/dist/relation-create-6WicROWA.js.map +1 -0
- package/dist/{relation-create-DDEYcKfw.cjs → relation-create-_SFuPQGM.cjs} +37 -3
- package/dist/relation-create-_SFuPQGM.cjs.map +1 -0
- package/dist/{relation-many-picker-DzuGJpEl.cjs → relation-many-picker-BBNsmkaX.cjs} +18 -11
- package/dist/relation-many-picker-BBNsmkaX.cjs.map +1 -0
- package/dist/{relation-many-picker-BJhaEqxz.js → relation-many-picker-mGEQDWo2.js} +18 -11
- package/dist/relation-many-picker-mGEQDWo2.js.map +1 -0
- package/dist/{relation-picker-D-tBBZW_.js → relation-picker-BbO41cFV.js} +19 -11
- package/dist/relation-picker-BbO41cFV.js.map +1 -0
- package/dist/{relation-picker-dialog-DoEY8EkH.js → relation-picker-dialog-BgSG_8bU.js} +5 -5
- package/dist/relation-picker-dialog-BgSG_8bU.js.map +1 -0
- package/dist/{relation-picker-dialog-9Vg5vvR1.cjs → relation-picker-dialog-Duqy2r0n.cjs} +5 -5
- package/dist/relation-picker-dialog-Duqy2r0n.cjs.map +1 -0
- package/dist/{relation-picker-DaIMzf2H.cjs → relation-picker-jk9D_uwn.cjs} +19 -11
- package/dist/relation-picker-jk9D_uwn.cjs.map +1 -0
- package/dist/{relation-select-Cr3CfNLy.cjs → relation-select-C3aV18kT.cjs} +2 -2
- package/dist/{relation-select-Cr3CfNLy.cjs.map → relation-select-C3aV18kT.cjs.map} +1 -1
- package/dist/{relation-select-B8Fq4o-t.js → relation-select-CjQT9LpS.js} +2 -2
- package/dist/{relation-select-B8Fq4o-t.js.map → relation-select-CjQT9LpS.js.map} +1 -1
- package/dist/{resource-page-router-Diw6S6TK.js → resource-page-router-CzaNATSp.js} +209 -30
- package/dist/resource-page-router-CzaNATSp.js.map +1 -0
- package/dist/{resource-page-router-C_kGW2o_.cjs → resource-page-router-dmVIeu5s.cjs} +235 -32
- package/dist/resource-page-router-dmVIeu5s.cjs.map +1 -0
- package/dist/{success-notices-Dl3M1fZP.cjs → success-notices-Cl4b6MLQ.cjs} +3 -2
- package/dist/success-notices-Cl4b6MLQ.cjs.map +1 -0
- package/dist/{success-notices-LDEgEkc4.js → success-notices-CrcWOGSo.js} +3 -2
- package/dist/success-notices-CrcWOGSo.js.map +1 -0
- package/dist/{use-dialog-filter-state-B_MqzHaT.js → use-dialog-filter-state-CJoeY-1j.js} +2 -2
- package/dist/{use-dialog-filter-state-B_MqzHaT.js.map → use-dialog-filter-state-CJoeY-1j.js.map} +1 -1
- package/dist/{use-dialog-filter-state-tNvqAAjh.cjs → use-dialog-filter-state-lsTJxrB4.cjs} +2 -2
- package/dist/{use-dialog-filter-state-tNvqAAjh.cjs.map → use-dialog-filter-state-lsTJxrB4.cjs.map} +1 -1
- package/dist/use-record-subtitle-BJ9rnj6F.cjs +67 -0
- package/dist/use-record-subtitle-BJ9rnj6F.cjs.map +1 -0
- package/dist/use-record-subtitle-w3ZhXNlf.js +50 -0
- package/dist/use-record-subtitle-w3ZhXNlf.js.map +1 -0
- package/package.json +9 -9
- package/dist/bulk-summary-BWSAS7k2.js.map +0 -1
- package/dist/bulk-summary-CrEij5vq.cjs.map +0 -1
- package/dist/check-confirm-dialog-C-NHgMST.cjs.map +0 -1
- package/dist/filter-builder-DFu0JP-1.cjs.map +0 -1
- package/dist/filter-builder-DaWHUZuV.js.map +0 -1
- package/dist/flow-screen-Bcz9NF2V.cjs.map +0 -1
- package/dist/flow-screen-DCdli6Ha.js.map +0 -1
- package/dist/page-flow-route-BHUEpn0I.js.map +0 -1
- package/dist/page-flow-route-DS8IVTVH.cjs.map +0 -1
- package/dist/place-node-CNb3w-c7.cjs.map +0 -1
- package/dist/place-node-CfzQvh4Y.js.map +0 -1
- package/dist/related-list-timeline-CRh7xwLD.js +0 -141
- package/dist/related-list-timeline-CRh7xwLD.js.map +0 -1
- package/dist/related-list-timeline-DsLdi8sv.cjs +0 -141
- package/dist/related-list-timeline-DsLdi8sv.cjs.map +0 -1
- package/dist/relation-create-C43cdPQt.js.map +0 -1
- package/dist/relation-create-DDEYcKfw.cjs.map +0 -1
- package/dist/relation-many-picker-BJhaEqxz.js.map +0 -1
- package/dist/relation-many-picker-DzuGJpEl.cjs.map +0 -1
- package/dist/relation-picker-D-tBBZW_.js.map +0 -1
- package/dist/relation-picker-DaIMzf2H.cjs.map +0 -1
- package/dist/relation-picker-dialog-9Vg5vvR1.cjs.map +0 -1
- package/dist/relation-picker-dialog-DoEY8EkH.js.map +0 -1
- package/dist/resource-page-router-C_kGW2o_.cjs.map +0 -1
- package/dist/resource-page-router-Diw6S6TK.js.map +0 -1
- package/dist/success-notices-Dl3M1fZP.cjs.map +0 -1
- package/dist/success-notices-LDEgEkc4.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"relation-picker-D-tBBZW_.js","names":[],"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 { 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 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 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 // 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 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 {/* A label that already is the id (a target whose `displayField` is\n `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 {/* 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 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 disabled={disabled}\n required={required}\n ariaInvalid={ariaInvalid}\n ariaDescribedBy={ariaDescribedBy}\n ariaErrorMessage={ariaErrorMessage}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"relation-picker-DaIMzf2H.cjs","names":["lazy","Suspense","Skeleton","routeResourceKey","useApp","useI18nCaps","useOptionalT","useRowTitle","resolveResourceLabel","useLinkComponent","React","useRelatedRecord","useRelationSelect","composeListFilter","normalizeFixedFilters","namesHiddenField","useList","Button","cn","IconChevronDown","IconLoader2","Popover","PopoverTrigger","RelationViewButton","PopoverContent","Command","CommandInput","CommandList","LoadError","CommandEmpty","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 { 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 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 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 // 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 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 {/* A label that already is the id (a target whose `displayField` is\n `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 {/* 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 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 disabled={disabled}\n required={required}\n ariaInvalid={ariaInvalid}\n ariaDescribedBy={ariaDescribedBy}\n ariaErrorMessage={ariaErrorMessage}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"relation-picker-dialog-9Vg5vvR1.cjs","names":["routeResourceKey","useApp","useI18nCaps","useOptionalT","useChromeT","useRowTitle","React","useDialogFilterState","useListAccess","columnName","useList","recordKeyOf","resolveResourceLabel","Dialog","DialogContent","DialogHeader","DialogTitle","DialogDescription","IconSearch","Input","FilterBuilder","Table","TableHeader","TableRow","TableHead","cn","resolveFieldLabel","declaredText","TableBody","TableCell","IconLoader2","ResolvedFieldDisplay","declaredField","IconCheck","IconChevronRight","DialogFooter","Button","RelationCreateButton","IconSelector","IconArrowUp","IconArrowDown","enumNamespaceOf","fieldOptionText","enumValueText"],"sources":["../src/fields/relation-picker-dialog.tsx"],"sourcesContent":["'use client';\nimport * as React from 'react';\nimport {\n IconArrowDown,\n IconArrowUp,\n IconCheck,\n IconChevronRight,\n IconSelector,\n IconLoader2,\n IconSearch,\n} from '@tabler/icons-react';\nimport {\n type FixedFilterInput,\n type DtoSource,\n type ScopeSource,\n declaredField,\n declaredText,\n enumNamespaceOf,\n enumValueText,\n fieldOptionText,\n resolveFieldLabel,\n resolveResourceLabel,\n routeResourceKey,\n type ResourceNameOrRef,\n columnName,\n} from '@burojs/core';\nimport {\n recordKeyOf,\n useApp,\n useChromeT,\n useI18nCaps,\n useList,\n useListAccess,\n useOptionalT,\n} from '@burojs/react';\nimport { useRowTitle } from '../resource/use-row-title.js';\nimport { ResolvedFieldDisplay } from './resolved-field.js';\nimport { cn } from '../lib/cn.js';\nimport { Button } from '../primitives/button.js';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../primitives/dialog.js';\nimport { Input } from '../primitives/input.js';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '../primitives/table.js';\nimport { FilterBuilder } from '../list-kit/filter-builder/filter-builder.js';\nimport { useDialogFilterState } from './use-dialog-filter-state.js';\nimport { RelationCreateButton, type SelectCreate } from './relation-create.js';\n\nconst noop = (): void => undefined;\n\n/**\n * Advanced-search modal for the relation picker (#11 / #9). The inline popover\n * handles the quick \"type a few letters\" case; this dialog is the escalation:\n * the full target list with SEARCH, per-column FILTERS, SORTING and PAGINATION,\n * plus a \"Create\" shortcut. Resolved generically from the registry so it works\n * for any resource with no per-field wiring.\n *\n * #9 — compact, and the table area is a FIXED height so paging never resizes /\n * jumps the dialog; responsive down to mobile.\n */\nexport interface RelationPickerDialogProps<N extends string = string> {\n to: ResourceNameOrRef<N>;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onSelect: (value: string | number) => void;\n /** Optional: receives the FULL chosen row (for prefilling sibling fields). */\n onSelectRow?: (row: Record<string, unknown>) => void;\n /**\n * Fallback search field — an OVERRIDE, not a required prop. The search box\n * expands through `useDialogFilterState`, which resolves what to search in\n * this order:\n * 1. the TARGET resource's own `views.list.search` declaration, if any —\n * always wins, regardless of this prop;\n * 2. this `searchField` prop, when the target declares no search;\n * 3. the target's `displayField` (itself falling back to `'name'`), when\n * neither of the above is available.\n *\n * A resolver-shaped `SearchDecl` has no single field that could be named\n * here, and a target with no search declaration at all should still be\n * searchable somehow — hence the two-step fallback instead of requiring\n * this prop outright.\n */\n searchField?: string;\n /**\n * Which field of the chosen row `onSelect` returns. Defaults to the target's id\n * field (`data.idField`, else `id`: a normal FK relation). Set to another column (e.g. `gtin`) for \"relate by a non-id\n * value\" pickers — the core mechanic behind the playground GTIN field.\n */\n valueField?: string;\n /**\n * Optional extra filters ANDed into every query (e.g. scope by product\n * group) — the same `FixedFilterInput` shape `useResourceList` and\n * `RelationPickerProps.fixedFilters` take: a bare AST expression\n * (`@burojs/core`'s `FilterExpression`, defaulting to `lock: true`, no\n * badge), or `{ filter, lock?, label? }` for finer control. `lock: false`\n * leaves the field open in the builder instead of hiding it; `label`\n * renders it as an undeletable badge.\n */\n fixedFilters?: readonly FixedFilterInput[];\n /**\n * The read scope of the dialog's list, as the select's own option reads take it. Default\n * `'context'`: the record around the select narrows the rows.\n */\n scope?: ScopeSource;\n /**\n * The record field the select writes (`useList`'s `scopeField`): the record's value of it never\n * narrows the rows offered for it. Absent: every field of the record narrows.\n */\n scopeField?: string;\n /**\n * The select's one pending create (P6-34), shared with its popover. With it the footer offers\n * \"+ Create\" (the target's `quick.create`, handed back through the select's own query); without\n * it the dialog offers none. The dialog is not a dismiss scope and holds no form: the sheet a\n * create opens stacks above it.\n */\n create?: SelectCreate;\n /** The trusted `defaults` of that create: the registry field's static `eq` literals (P6-31). */\n createSeeds?: Readonly<Record<string, unknown>>;\n /** The form field holding the key (the create button's test id). */\n fieldName?: string;\n /** Runs before a create opens: the select returns focus to its trigger. */\n onBeforeCreate?: () => void;\n pageSize?: number;\n /**\n * A to-many select's choice (P7 3.5): a click toggles the row in `selected` through `onToggle`\n * and the dialog stays open until the user is done. Without it, a click picks one row and closes.\n */\n multiple?: boolean;\n /** The keys already chosen (`multiple` only): their rows are marked. */\n selected?: readonly (string | number)[];\n /** A row toggled in `multiple` mode: its value, and the row itself. */\n onToggle?: (value: string | number, row: Record<string, unknown>) => void;\n /**\n * `multiple` only: the Select button confirms the choice through this instead of closing the\n * dialog with `onOpenChange(false)`, so a confirm and a dismissal can be told apart.\n */\n onDone?: () => void;\n /**\n * Asked once per shown page with the keys of its rows: rows whose key is in the resolved set\n * are drawn checked and disabled (a link panel's already-linked targets). Until it answers the\n * page's rows cannot be picked and the footer says the rows are being checked; when it fails\n * none of them can, and the footer says so with a Retry.\n */\n disabledKeys?: (\n keys: readonly (string | number)[],\n signal: AbortSignal,\n ) => Promise<ReadonlySet<string>>;\n /**\n * With `disabledKeys`: the footer's note for the page's disabled rows, given how many there\n * are (the caller says what they are, \"3 already linked\"). Absent: no note.\n */\n disabledLabel?: (count: number) => React.ReactNode;\n}\n\nconst DEBOUNCE_MS = 250;\n\n/** The disabled rows of a page: the keys asked (and the attempt), and the answer. */\ninterface DisabledState {\n readonly asked: string;\n readonly attempt: number;\n readonly keys: ReadonlySet<string> | 'failed';\n}\n\ntype EnumOption = { value: string; label: string };\n\nexport function RelationPickerDialog<const N extends string = string>({\n to: toInput,\n open,\n onOpenChange,\n onSelect,\n onSelectRow,\n searchField,\n valueField: valueFieldProp,\n fixedFilters,\n scope = 'context',\n scopeField,\n create,\n createSeeds,\n fieldName,\n onBeforeCreate,\n pageSize = 8,\n multiple = false,\n selected,\n onToggle,\n onDone,\n disabledKeys,\n disabledLabel,\n}: RelationPickerDialogProps<N>): React.ReactElement {\n const to = routeResourceKey(toInput);\n const app = useApp();\n const caps = useI18nCaps();\n // The target's own caps: a column header reads `resources.<to>.fields.<f>.label` first.\n const targetCaps = useI18nCaps(to);\n const t = useOptionalT();\n const chromeT = useChromeT();\n const tr = (key: string, params?: Record<string, unknown>, fallback?: string) =>\n t?.(key, { params, defaultValue: fallback }) ?? fallback ?? key;\n const targetDef = app.registry.get(to);\n const rowTitle = useRowTitle(to, targetDef?.displayField);\n const declaredIdField = (targetDef?.data as { readonly idField?: unknown } | undefined)?.idField;\n const idField =\n typeof declaredIdField === 'string' && declaredIdField.length > 0 ? declaredIdField : 'id';\n const valueField = valueFieldProp ?? idField;\n\n const [search, setSearchInput] = React.useState('');\n const [debounced, setDebounced] = React.useState('');\n const [sort, setSort] = React.useState<{ field: string; dir: 'asc' | 'desc' }>({\n field: 'id',\n dir: 'desc',\n });\n\n const displayField = targetDef?.displayField ?? 'name';\n\n // Resolution order (see `RelationPickerDialogProps.searchField`'s doc):\n // the target's own `views.list.search` declaration always wins inside\n // `useDialogFilterState`; this `searchField` prop is only consulted THERE\n // when the target declares no search at all, and `displayField` is what\n // this dialog hands the hook when the caller didn't override either — so\n // a target with no declaration and no override is still searchable.\n //\n // `enabled: open` — `RelationPickerDialog` is always mounted by\n // `<RelationPicker>` (it's a controlled dialog, not conditionally\n // rendered), so without this gate the field-derivation walk inside\n // `useFilterFields` (`categorizeField` over every field of the target)\n // would run on every mount and every `fixedFilters`-identity change of\n // EVERY relation field on a form, whether its dialog ever opens or not.\n // Gating recomputes in full the moment `open` flips true — same render,\n // before the dialog shows anything, so nothing it displays changes.\n const filterState = useDialogFilterState(to, {\n fixedFilters,\n searchField: searchField ?? displayField,\n enabled: open,\n });\n\n React.useEffect(() => {\n const h = setTimeout(() => setDebounced(search), DEBOUNCE_MS);\n return () => clearTimeout(h);\n }, [search]);\n\n // The debounced text is the one thing this dialog composes itself — the\n // rest of the filter (fixed scope, the builder's group, and the actual\n // `fixed → search → group` composition) all live in `useDialogFilterState`\n // now, via the SAME `composeListFilter` core the list page uses.\n React.useEffect(() => {\n filterState.setSearch(debounced);\n // `filterState.setSearch` is stable (useCallback, empty deps) — only\n // `debounced` driving this is real; including the whole `filterState`\n // object would refire on every recompute of its OWN memo (its `filter`\n // depends on this very effect's result), which is not what \"debounced\n // changed\" means.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debounced]);\n\n // Reset volatile state each time the dialog opens.\n React.useEffect(() => {\n if (open) {\n setSearchInput('');\n setDebounced('');\n filterState.reset();\n }\n // `filterState.reset` is stable (useCallback, empty deps); `filterState`\n // itself is excluded for the same reason as the effect above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n\n const listAccess = useListAccess(to);\n const extraColumns = React.useMemo(() => {\n const declared = (targetDef?.views?.list?.columns ?? []).map(columnName);\n // The same predicate as the list table: a pending decision shows no extra column.\n return declared\n .filter((c) => c !== 'id' && c !== displayField)\n .filter((c) => listAccess.status === 'ready' && listAccess.columnVisible(c))\n .slice(0, 2);\n }, [targetDef, displayField, listAccess]);\n const titleSortable = listAccess.status === 'ready' && listAccess.fieldVisible(displayField);\n // A sort on a field the user may not view is an ordering oracle: it is dropped, never sent.\n const effectiveSortField =\n sort.field === 'id' ||\n (sort.field === displayField ? titleSortable : extraColumns.includes(sort.field))\n ? sort.field\n : 'id';\n\n const query = useList(to, {\n ...(filterState.filter ? { filter: filterState.filter } : {}),\n sort: [\n {\n field: effectiveSortField,\n direction: effectiveSortField === sort.field ? sort.dir : 'desc',\n },\n ],\n pagination: { kind: 'offset', page: filterState.page, pageSize },\n scope,\n ...(scopeField === undefined ? {} : { scopeField }),\n enabled: open && targetDef != null && !filterState.blocked,\n });\n\n const rows = (query.data?.data ?? []) as Array<{ id: unknown } & Record<string, unknown>>;\n // Unknown-total contract: a provider that cannot count omits `total` (Hasura\n // `noAggregate` targets such as `users` / `code_code`). Deriving `totalPages`\n // from a `?? 0` fallback pinned it to 1 and disabled Next forever. With an\n // unknown total we page forward while the page is full.\n const total = query.data?.total;\n const knownTotal = typeof total === 'number';\n const totalPages = knownTotal ? Math.max(1, Math.ceil(total / pageSize)) : undefined;\n const pageIsFull = rows.length >= pageSize;\n const hasNextPage = knownTotal ? filterState.page < (totalPages as number) : pageIsFull;\n const isLoading = query.isLoading || query.isFetching;\n\n // The key a row is chosen, selected and disabled by: the target's idField unless a value field\n // names another column.\n const keyOf = (row: Record<string, unknown>): unknown =>\n valueField === idField ? recordKeyOf(targetDef, row) : row[valueField];\n // The page's keys, asked of `disabledKeys` once per distinct set.\n const pageKeys = rows\n .map(keyOf)\n .filter((key): key is string | number => typeof key === 'string' || typeof key === 'number');\n const asked = JSON.stringify(pageKeys);\n const [disabled, setDisabled] = React.useState<DisabledState | undefined>(undefined);\n // A Retry after a failed answer asks the same keys again.\n const [attempt, setAttempt] = React.useState(0);\n // biome-ignore lint/correctness/useExhaustiveDependencies: keyed by the page's keys, not the rows' identity\n React.useEffect(() => {\n if (disabledKeys === undefined || pageKeys.length === 0) return;\n const controller = new AbortController();\n disabledKeys(pageKeys, controller.signal).then(\n (keys) => {\n if (!controller.signal.aborted) setDisabled({ asked, attempt, keys });\n },\n () => {\n // Fails closed: a row that may already be linked is never offered.\n if (!controller.signal.aborted) setDisabled({ asked, attempt, keys: 'failed' });\n },\n );\n return () => controller.abort();\n }, [asked, attempt, disabledKeys]);\n // The answer for the page shown now; undefined while it is being asked.\n const answer =\n disabledKeys === undefined ||\n pageKeys.length === 0 ||\n disabled === undefined ||\n disabled.asked !== asked ||\n disabled.attempt !== attempt\n ? undefined\n : disabled.keys;\n const checking = disabledKeys !== undefined && pageKeys.length > 0 && answer === undefined;\n const checkFailed = answer === 'failed';\n const isDisabled = (row: Record<string, unknown>): boolean => {\n if (disabledKeys === undefined) return false;\n if (answer === undefined || answer === 'failed') return true;\n return answer.has(String(keyOf(row)));\n };\n const disabledCount =\n answer === undefined || answer === 'failed' ? 0 : rows.filter(isDisabled).length;\n\n const toggleSort = (field: string) => {\n setSort((prev) =>\n prev.field === field\n ? { field, dir: prev.dir === 'asc' ? 'desc' : 'asc' }\n : { field, dir: 'asc' },\n );\n };\n\n const targetLabel = resolveResourceLabel(caps, to, 'single', targetDef);\n const colCount = 2 + extraColumns.length;\n\n const pick = (row: { id: unknown } & Record<string, unknown>) => {\n if (isDisabled(row)) return;\n const picked = keyOf(row);\n // A row without the value the field stores is never written as its id instead (spec 4.2).\n if (picked === null || picked === undefined || picked === '') return;\n if (multiple) {\n onToggle?.(picked as string | number, row as Record<string, unknown>);\n return;\n }\n onSelect(picked as string | number);\n onSelectRow?.(row as Record<string, unknown>);\n onOpenChange(false);\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange} modal={create?.pending !== true}>\n <DialogContent\n // While a create of this select is open its sheet stacks above this dialog and stays\n // interactive: the dialog neither traps focus nor closes on a click outside, and Escape\n // closes only the sheet (P6-67).\n {...(create?.pending === true\n ? {\n onInteractOutside: (event: Event) => event.preventDefault(),\n onEscapeKeyDown: (event: KeyboardEvent) => event.preventDefault(),\n }\n : {})}\n className=\"flex max-h-[85vh] w-[calc(100vw-1.5rem)] max-w-3xl flex-col gap-0 overflow-hidden p-0\"\n data-testid={`relation-picker-dialog:${to}`}\n >\n <DialogHeader className=\"border-b px-4 py-3\">\n <DialogTitle className=\"text-base\">\n {tr('relationPicker.title', { name: targetLabel }, `Select: ${targetLabel}`)}\n </DialogTitle>\n <DialogDescription className=\"sr-only\">\n {tr('relationPicker.advancedSearch', undefined, 'Advanced search')}\n </DialogDescription>\n </DialogHeader>\n\n {/* Search + the filter builder (#9 / Task 5 SP3) */}\n <div className=\"flex flex-wrap items-center gap-2 border-b px-4 py-2.5\">\n {filterState.searchEnabled && (\n <div className=\"relative min-w-[12rem] flex-1\">\n <IconSearch className=\"pointer-events-none absolute left-2.5 top-2.5 size-4 text-muted-foreground\" />\n <Input\n autoFocus\n value={search}\n onChange={(e) => setSearchInput(e.target.value)}\n placeholder={tr('toolbar.search', { name: targetLabel }, `Search ${targetLabel}…`)}\n data-testid={`relation-picker-dialog:${to}:search`}\n className=\"h-9 pl-8\"\n />\n </div>\n )}\n {/*\n No `pinnedFields`/`pinnedValue`/`setPinnedValue` — this dialog has\n no pinned slot (`useDialogFilterState`'s doc: a modal has no\n spare real estate for always-visible facets), so `FilterBuilder`\n falls back to its own stable empty-array defaults.\n */}\n <FilterBuilder\n group={filterState.group}\n setGroup={filterState.setGroup}\n available={filterState.available}\n fixed={filterState.fixed}\n setPage={filterState.setPage}\n testIdBase={`relation-picker-dialog:${to}:filter`}\n />\n </div>\n\n {/* Fixed-height table area — constant height so paging never jumps. */}\n <div className=\"h-[46vh] min-h-[16rem] overflow-auto\">\n <Table>\n <TableHeader className=\"sticky top-0 z-10 bg-background shadow-[inset_0_-1px_0_var(--border)]\">\n <TableRow className=\"hover:bg-transparent\">\n <TableHead\n className=\"h-9 w-14 cursor-pointer select-none\"\n onClick={() => toggleSort('id')}\n >\n {tr('relationPicker.idColumn', undefined, 'ID')}{' '}\n <SortIcon field=\"id\" sort={sort} />\n </TableHead>\n <TableHead\n className={cn('h-9 select-none', titleSortable && 'cursor-pointer')}\n {...(titleSortable ? { onClick: () => toggleSort(displayField) } : {})}\n >\n {targetLabel} {titleSortable && <SortIcon field={displayField} sort={sort} />}\n </TableHead>\n {extraColumns.map((c) => (\n <TableHead\n key={c}\n className=\"h-9 cursor-pointer select-none\"\n onClick={() => toggleSort(c)}\n >\n {resolveFieldLabel(\n targetCaps,\n c,\n declaredText('field', targetDef?.fields?.[c], 'label'),\n )}\n <SortIcon field={c} sort={sort} />\n </TableHead>\n ))}\n <TableHead className=\"h-9 w-8\" />\n </TableRow>\n </TableHeader>\n <TableBody>\n {isLoading ? (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell colSpan={colCount + 1} className=\"h-64 text-center\">\n <IconLoader2 className=\"mx-auto size-4 animate-spin text-muted-foreground\" />\n </TableCell>\n </TableRow>\n ) : rows.length === 0 ? (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell\n colSpan={colCount + 1}\n className=\"h-64 text-center text-sm text-muted-foreground\"\n >\n {tr('relation.noResults', undefined, 'No results.')}\n </TableCell>\n </TableRow>\n ) : (\n rows.map((row) => {\n const idStr = String(recordKeyOf(targetDef, row));\n const rowDisabled = isDisabled(row);\n // A disabled row is drawn checked: it is already chosen elsewhere.\n const checked =\n rowDisabled ||\n (selected ?? []).some((key) => String(key) === String(keyOf(row)));\n return (\n <TableRow\n key={idStr}\n className={cn(\n rowDisabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer',\n )}\n data-testid={`relation-picker-dialog:${to}:row:${idStr}`}\n {...(multiple ? { 'aria-selected': checked } : {})}\n {...(rowDisabled ? { 'aria-disabled': true } : {})}\n onClick={() => pick(row)}\n >\n <TableCell className=\"py-2 tabular-nums text-muted-foreground\">\n {tr('relationPicker.recordNumber', { id: idStr }, `#${idStr}`)}\n </TableCell>\n <TableCell className=\"py-2 font-medium\">{rowTitle(row)}</TableCell>\n {extraColumns.map((c) => (\n <TableCell key={c} className=\"py-2 text-muted-foreground\">\n {/* The list's display renderers: locale numbers and dates, enum labels. */}\n <ResolvedFieldDisplay\n resource={targetDef?.ref ?? { namespace: 'default', id: to }}\n name={c}\n field={\n (targetDef === undefined\n ? undefined\n : declaredField(targetDef as unknown as DtoSource, c)) ??\n (targetDef?.fields?.[c] as never) ?? { type: 'unknown' }\n }\n value={row[c]}\n record={row}\n fallback={formatCell(row[c], targetDef?.fields?.[c], (ns) =>\n app.registry.enums.get(ns),\n )}\n />\n </TableCell>\n ))}\n <TableCell className=\"py-2 text-right\">\n {multiple ? (\n <IconCheck\n className={cn('ml-auto size-4', checked ? 'opacity-100' : 'opacity-0')}\n aria-hidden\n />\n ) : (\n <IconChevronRight className=\"ml-auto size-4 text-muted-foreground/50\" />\n )}\n </TableCell>\n </TableRow>\n );\n })\n )}\n </TableBody>\n </Table>\n </div>\n\n <DialogFooter className=\"flex-row items-center justify-between gap-2 border-t px-4 py-2.5 sm:justify-between\">\n {/*\n A wrapper that is always there: the footer is `justify-between`, so dropping its first\n child (no create offered) would slide the pager to the left edge.\n */}\n <div className=\"flex items-center gap-3\">\n {checking && (\n <span\n role=\"status\"\n className=\"inline-flex items-center gap-1.5 text-sm text-muted-foreground\"\n data-testid={`relation-picker-dialog:${to}:disabled-checking`}\n >\n <IconLoader2 className=\"size-3.5 animate-spin\" aria-hidden=\"true\" />\n {tr('relationPicker.checking', undefined, 'Checking these rows…')}\n </span>\n )}\n {checkFailed && (\n <span\n role=\"alert\"\n className=\"inline-flex items-center gap-2 text-sm text-destructive\"\n data-testid={`relation-picker-dialog:${to}:disabled-failed`}\n >\n {tr(\n 'relationPicker.checkFailed',\n undefined,\n 'These rows could not be checked, so none can be chosen.',\n )}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7\"\n onClick={() => setAttempt((n) => n + 1)}\n data-testid={`relation-picker-dialog:${to}:disabled-retry`}\n >\n {chromeT('actions.retry', { defaultValue: 'Retry' })}\n </Button>\n </span>\n )}\n {disabledCount > 0 && disabledLabel !== undefined && (\n <span\n className=\"text-sm text-muted-foreground\"\n data-testid={`relation-picker-dialog:${to}:disabled-count`}\n >\n {disabledLabel(disabledCount)}\n </span>\n )}\n {create !== undefined && (\n <RelationCreateButton\n to={to}\n fieldName={fieldName ?? to}\n create={create}\n seeds={createSeeds}\n search={search}\n beforeOpen={onBeforeCreate ?? noop}\n className=\"inline-flex items-center gap-1 whitespace-nowrap text-sm text-foreground hover:underline disabled:opacity-50\"\n />\n )}\n </div>\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <span className=\"hidden tabular-nums sm:inline\">\n {knownTotal\n ? tr(\n 'pagination.pageOf',\n { page: filterState.page, total: totalPages },\n `${filterState.page} / ${totalPages}`,\n )\n : tr('pagination.pageOnly', { page: filterState.page }, `${filterState.page}`)}\n </span>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n className=\"h-8\"\n disabled={filterState.page <= 1}\n onClick={() => filterState.setPage(Math.max(1, filterState.page - 1))}\n data-testid={`relation-picker-dialog:${to}:prev`}\n >\n {tr('relationPicker.prev', undefined, 'Prev')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n className=\"h-8\"\n disabled={!hasNextPage}\n onClick={() => filterState.setPage(filterState.page + 1)}\n data-testid={`relation-picker-dialog:${to}:next`}\n >\n {tr('relationPicker.next', undefined, 'Next')}\n </Button>\n {multiple && (\n <Button\n type=\"button\"\n size=\"sm\"\n className=\"h-8\"\n onClick={() => (onDone === undefined ? onOpenChange(false) : onDone())}\n data-testid={`relation-picker-dialog:${to}:done`}\n >\n {tr('relationPicker.select', undefined, 'Select')}\n </Button>\n )}\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n/**\n * A column's sort arrow. Declared here, not inside the dialog: a component made in a render is a\n * new type on every render, so React replaced these icons each time the dialog rendered — and a\n * node removed from the dialog while focus was crossing into the filter popover (which is portaled\n * outside it, so focus sits on `body` for that moment) makes the dialog's focus scope take focus\n * back, which closed the popover on any click inside it.\n */\nfunction SortIcon({\n field,\n sort,\n}: {\n readonly field: string;\n readonly sort: { readonly field: string; readonly dir: 'asc' | 'desc' };\n}): React.ReactElement {\n if (sort.field !== field) return <IconSelector className=\"ml-1 inline size-3 opacity-40\" />;\n return sort.dir === 'asc' ? (\n <IconArrowUp className=\"ml-1 inline size-3\" />\n ) : (\n <IconArrowDown className=\"ml-1 inline size-3\" />\n );\n}\n\nfunction formatCell(\n value: unknown,\n field: unknown,\n enumOf: (namespace: string) => Parameters<typeof enumValueText>[0],\n): string {\n if (value == null || value === '') return '—';\n if (typeof value === 'boolean') return value ? '✓' : '—';\n // Map an option value → its human label: the field's `texts.options`, the bound enum's\n // `texts`, else a label the field's `meta.options` carries (adopter data).\n const namespace =\n field !== null && typeof field === 'object'\n ? enumNamespaceOf(field as Parameters<typeof enumNamespaceOf>[0])\n : undefined;\n const declared =\n fieldOptionText(field, String(value)) ??\n (namespace === undefined ? undefined : enumValueText(enumOf(namespace), String(value)));\n if (declared !== undefined) return declared;\n const options = (field as { meta?: { options?: EnumOption[] } } | undefined)?.meta?.options;\n if (Array.isArray(options)) {\n const hit = options.find((o) => o.value === String(value));\n if (hit && typeof hit.label === 'string') return hit.label;\n }\n const s = String(value);\n return s.length > 40 ? `${s.slice(0, 40)}…` : s;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"relation-picker-dialog-DoEY8EkH.js","names":[],"sources":["../src/fields/relation-picker-dialog.tsx"],"sourcesContent":["'use client';\nimport * as React from 'react';\nimport {\n IconArrowDown,\n IconArrowUp,\n IconCheck,\n IconChevronRight,\n IconSelector,\n IconLoader2,\n IconSearch,\n} from '@tabler/icons-react';\nimport {\n type FixedFilterInput,\n type DtoSource,\n type ScopeSource,\n declaredField,\n declaredText,\n enumNamespaceOf,\n enumValueText,\n fieldOptionText,\n resolveFieldLabel,\n resolveResourceLabel,\n routeResourceKey,\n type ResourceNameOrRef,\n columnName,\n} from '@burojs/core';\nimport {\n recordKeyOf,\n useApp,\n useChromeT,\n useI18nCaps,\n useList,\n useListAccess,\n useOptionalT,\n} from '@burojs/react';\nimport { useRowTitle } from '../resource/use-row-title.js';\nimport { ResolvedFieldDisplay } from './resolved-field.js';\nimport { cn } from '../lib/cn.js';\nimport { Button } from '../primitives/button.js';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../primitives/dialog.js';\nimport { Input } from '../primitives/input.js';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '../primitives/table.js';\nimport { FilterBuilder } from '../list-kit/filter-builder/filter-builder.js';\nimport { useDialogFilterState } from './use-dialog-filter-state.js';\nimport { RelationCreateButton, type SelectCreate } from './relation-create.js';\n\nconst noop = (): void => undefined;\n\n/**\n * Advanced-search modal for the relation picker (#11 / #9). The inline popover\n * handles the quick \"type a few letters\" case; this dialog is the escalation:\n * the full target list with SEARCH, per-column FILTERS, SORTING and PAGINATION,\n * plus a \"Create\" shortcut. Resolved generically from the registry so it works\n * for any resource with no per-field wiring.\n *\n * #9 — compact, and the table area is a FIXED height so paging never resizes /\n * jumps the dialog; responsive down to mobile.\n */\nexport interface RelationPickerDialogProps<N extends string = string> {\n to: ResourceNameOrRef<N>;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onSelect: (value: string | number) => void;\n /** Optional: receives the FULL chosen row (for prefilling sibling fields). */\n onSelectRow?: (row: Record<string, unknown>) => void;\n /**\n * Fallback search field — an OVERRIDE, not a required prop. The search box\n * expands through `useDialogFilterState`, which resolves what to search in\n * this order:\n * 1. the TARGET resource's own `views.list.search` declaration, if any —\n * always wins, regardless of this prop;\n * 2. this `searchField` prop, when the target declares no search;\n * 3. the target's `displayField` (itself falling back to `'name'`), when\n * neither of the above is available.\n *\n * A resolver-shaped `SearchDecl` has no single field that could be named\n * here, and a target with no search declaration at all should still be\n * searchable somehow — hence the two-step fallback instead of requiring\n * this prop outright.\n */\n searchField?: string;\n /**\n * Which field of the chosen row `onSelect` returns. Defaults to the target's id\n * field (`data.idField`, else `id`: a normal FK relation). Set to another column (e.g. `gtin`) for \"relate by a non-id\n * value\" pickers — the core mechanic behind the playground GTIN field.\n */\n valueField?: string;\n /**\n * Optional extra filters ANDed into every query (e.g. scope by product\n * group) — the same `FixedFilterInput` shape `useResourceList` and\n * `RelationPickerProps.fixedFilters` take: a bare AST expression\n * (`@burojs/core`'s `FilterExpression`, defaulting to `lock: true`, no\n * badge), or `{ filter, lock?, label? }` for finer control. `lock: false`\n * leaves the field open in the builder instead of hiding it; `label`\n * renders it as an undeletable badge.\n */\n fixedFilters?: readonly FixedFilterInput[];\n /**\n * The read scope of the dialog's list, as the select's own option reads take it. Default\n * `'context'`: the record around the select narrows the rows.\n */\n scope?: ScopeSource;\n /**\n * The record field the select writes (`useList`'s `scopeField`): the record's value of it never\n * narrows the rows offered for it. Absent: every field of the record narrows.\n */\n scopeField?: string;\n /**\n * The select's one pending create (P6-34), shared with its popover. With it the footer offers\n * \"+ Create\" (the target's `quick.create`, handed back through the select's own query); without\n * it the dialog offers none. The dialog is not a dismiss scope and holds no form: the sheet a\n * create opens stacks above it.\n */\n create?: SelectCreate;\n /** The trusted `defaults` of that create: the registry field's static `eq` literals (P6-31). */\n createSeeds?: Readonly<Record<string, unknown>>;\n /** The form field holding the key (the create button's test id). */\n fieldName?: string;\n /** Runs before a create opens: the select returns focus to its trigger. */\n onBeforeCreate?: () => void;\n pageSize?: number;\n /**\n * A to-many select's choice (P7 3.5): a click toggles the row in `selected` through `onToggle`\n * and the dialog stays open until the user is done. Without it, a click picks one row and closes.\n */\n multiple?: boolean;\n /** The keys already chosen (`multiple` only): their rows are marked. */\n selected?: readonly (string | number)[];\n /** A row toggled in `multiple` mode: its value, and the row itself. */\n onToggle?: (value: string | number, row: Record<string, unknown>) => void;\n /**\n * `multiple` only: the Select button confirms the choice through this instead of closing the\n * dialog with `onOpenChange(false)`, so a confirm and a dismissal can be told apart.\n */\n onDone?: () => void;\n /**\n * Asked once per shown page with the keys of its rows: rows whose key is in the resolved set\n * are drawn checked and disabled (a link panel's already-linked targets). Until it answers the\n * page's rows cannot be picked and the footer says the rows are being checked; when it fails\n * none of them can, and the footer says so with a Retry.\n */\n disabledKeys?: (\n keys: readonly (string | number)[],\n signal: AbortSignal,\n ) => Promise<ReadonlySet<string>>;\n /**\n * With `disabledKeys`: the footer's note for the page's disabled rows, given how many there\n * are (the caller says what they are, \"3 already linked\"). Absent: no note.\n */\n disabledLabel?: (count: number) => React.ReactNode;\n}\n\nconst DEBOUNCE_MS = 250;\n\n/** The disabled rows of a page: the keys asked (and the attempt), and the answer. */\ninterface DisabledState {\n readonly asked: string;\n readonly attempt: number;\n readonly keys: ReadonlySet<string> | 'failed';\n}\n\ntype EnumOption = { value: string; label: string };\n\nexport function RelationPickerDialog<const N extends string = string>({\n to: toInput,\n open,\n onOpenChange,\n onSelect,\n onSelectRow,\n searchField,\n valueField: valueFieldProp,\n fixedFilters,\n scope = 'context',\n scopeField,\n create,\n createSeeds,\n fieldName,\n onBeforeCreate,\n pageSize = 8,\n multiple = false,\n selected,\n onToggle,\n onDone,\n disabledKeys,\n disabledLabel,\n}: RelationPickerDialogProps<N>): React.ReactElement {\n const to = routeResourceKey(toInput);\n const app = useApp();\n const caps = useI18nCaps();\n // The target's own caps: a column header reads `resources.<to>.fields.<f>.label` first.\n const targetCaps = useI18nCaps(to);\n const t = useOptionalT();\n const chromeT = useChromeT();\n const tr = (key: string, params?: Record<string, unknown>, fallback?: string) =>\n t?.(key, { params, defaultValue: fallback }) ?? fallback ?? key;\n const targetDef = app.registry.get(to);\n const rowTitle = useRowTitle(to, targetDef?.displayField);\n const declaredIdField = (targetDef?.data as { readonly idField?: unknown } | undefined)?.idField;\n const idField =\n typeof declaredIdField === 'string' && declaredIdField.length > 0 ? declaredIdField : 'id';\n const valueField = valueFieldProp ?? idField;\n\n const [search, setSearchInput] = React.useState('');\n const [debounced, setDebounced] = React.useState('');\n const [sort, setSort] = React.useState<{ field: string; dir: 'asc' | 'desc' }>({\n field: 'id',\n dir: 'desc',\n });\n\n const displayField = targetDef?.displayField ?? 'name';\n\n // Resolution order (see `RelationPickerDialogProps.searchField`'s doc):\n // the target's own `views.list.search` declaration always wins inside\n // `useDialogFilterState`; this `searchField` prop is only consulted THERE\n // when the target declares no search at all, and `displayField` is what\n // this dialog hands the hook when the caller didn't override either — so\n // a target with no declaration and no override is still searchable.\n //\n // `enabled: open` — `RelationPickerDialog` is always mounted by\n // `<RelationPicker>` (it's a controlled dialog, not conditionally\n // rendered), so without this gate the field-derivation walk inside\n // `useFilterFields` (`categorizeField` over every field of the target)\n // would run on every mount and every `fixedFilters`-identity change of\n // EVERY relation field on a form, whether its dialog ever opens or not.\n // Gating recomputes in full the moment `open` flips true — same render,\n // before the dialog shows anything, so nothing it displays changes.\n const filterState = useDialogFilterState(to, {\n fixedFilters,\n searchField: searchField ?? displayField,\n enabled: open,\n });\n\n React.useEffect(() => {\n const h = setTimeout(() => setDebounced(search), DEBOUNCE_MS);\n return () => clearTimeout(h);\n }, [search]);\n\n // The debounced text is the one thing this dialog composes itself — the\n // rest of the filter (fixed scope, the builder's group, and the actual\n // `fixed → search → group` composition) all live in `useDialogFilterState`\n // now, via the SAME `composeListFilter` core the list page uses.\n React.useEffect(() => {\n filterState.setSearch(debounced);\n // `filterState.setSearch` is stable (useCallback, empty deps) — only\n // `debounced` driving this is real; including the whole `filterState`\n // object would refire on every recompute of its OWN memo (its `filter`\n // depends on this very effect's result), which is not what \"debounced\n // changed\" means.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debounced]);\n\n // Reset volatile state each time the dialog opens.\n React.useEffect(() => {\n if (open) {\n setSearchInput('');\n setDebounced('');\n filterState.reset();\n }\n // `filterState.reset` is stable (useCallback, empty deps); `filterState`\n // itself is excluded for the same reason as the effect above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n\n const listAccess = useListAccess(to);\n const extraColumns = React.useMemo(() => {\n const declared = (targetDef?.views?.list?.columns ?? []).map(columnName);\n // The same predicate as the list table: a pending decision shows no extra column.\n return declared\n .filter((c) => c !== 'id' && c !== displayField)\n .filter((c) => listAccess.status === 'ready' && listAccess.columnVisible(c))\n .slice(0, 2);\n }, [targetDef, displayField, listAccess]);\n const titleSortable = listAccess.status === 'ready' && listAccess.fieldVisible(displayField);\n // A sort on a field the user may not view is an ordering oracle: it is dropped, never sent.\n const effectiveSortField =\n sort.field === 'id' ||\n (sort.field === displayField ? titleSortable : extraColumns.includes(sort.field))\n ? sort.field\n : 'id';\n\n const query = useList(to, {\n ...(filterState.filter ? { filter: filterState.filter } : {}),\n sort: [\n {\n field: effectiveSortField,\n direction: effectiveSortField === sort.field ? sort.dir : 'desc',\n },\n ],\n pagination: { kind: 'offset', page: filterState.page, pageSize },\n scope,\n ...(scopeField === undefined ? {} : { scopeField }),\n enabled: open && targetDef != null && !filterState.blocked,\n });\n\n const rows = (query.data?.data ?? []) as Array<{ id: unknown } & Record<string, unknown>>;\n // Unknown-total contract: a provider that cannot count omits `total` (Hasura\n // `noAggregate` targets such as `users` / `code_code`). Deriving `totalPages`\n // from a `?? 0` fallback pinned it to 1 and disabled Next forever. With an\n // unknown total we page forward while the page is full.\n const total = query.data?.total;\n const knownTotal = typeof total === 'number';\n const totalPages = knownTotal ? Math.max(1, Math.ceil(total / pageSize)) : undefined;\n const pageIsFull = rows.length >= pageSize;\n const hasNextPage = knownTotal ? filterState.page < (totalPages as number) : pageIsFull;\n const isLoading = query.isLoading || query.isFetching;\n\n // The key a row is chosen, selected and disabled by: the target's idField unless a value field\n // names another column.\n const keyOf = (row: Record<string, unknown>): unknown =>\n valueField === idField ? recordKeyOf(targetDef, row) : row[valueField];\n // The page's keys, asked of `disabledKeys` once per distinct set.\n const pageKeys = rows\n .map(keyOf)\n .filter((key): key is string | number => typeof key === 'string' || typeof key === 'number');\n const asked = JSON.stringify(pageKeys);\n const [disabled, setDisabled] = React.useState<DisabledState | undefined>(undefined);\n // A Retry after a failed answer asks the same keys again.\n const [attempt, setAttempt] = React.useState(0);\n // biome-ignore lint/correctness/useExhaustiveDependencies: keyed by the page's keys, not the rows' identity\n React.useEffect(() => {\n if (disabledKeys === undefined || pageKeys.length === 0) return;\n const controller = new AbortController();\n disabledKeys(pageKeys, controller.signal).then(\n (keys) => {\n if (!controller.signal.aborted) setDisabled({ asked, attempt, keys });\n },\n () => {\n // Fails closed: a row that may already be linked is never offered.\n if (!controller.signal.aborted) setDisabled({ asked, attempt, keys: 'failed' });\n },\n );\n return () => controller.abort();\n }, [asked, attempt, disabledKeys]);\n // The answer for the page shown now; undefined while it is being asked.\n const answer =\n disabledKeys === undefined ||\n pageKeys.length === 0 ||\n disabled === undefined ||\n disabled.asked !== asked ||\n disabled.attempt !== attempt\n ? undefined\n : disabled.keys;\n const checking = disabledKeys !== undefined && pageKeys.length > 0 && answer === undefined;\n const checkFailed = answer === 'failed';\n const isDisabled = (row: Record<string, unknown>): boolean => {\n if (disabledKeys === undefined) return false;\n if (answer === undefined || answer === 'failed') return true;\n return answer.has(String(keyOf(row)));\n };\n const disabledCount =\n answer === undefined || answer === 'failed' ? 0 : rows.filter(isDisabled).length;\n\n const toggleSort = (field: string) => {\n setSort((prev) =>\n prev.field === field\n ? { field, dir: prev.dir === 'asc' ? 'desc' : 'asc' }\n : { field, dir: 'asc' },\n );\n };\n\n const targetLabel = resolveResourceLabel(caps, to, 'single', targetDef);\n const colCount = 2 + extraColumns.length;\n\n const pick = (row: { id: unknown } & Record<string, unknown>) => {\n if (isDisabled(row)) return;\n const picked = keyOf(row);\n // A row without the value the field stores is never written as its id instead (spec 4.2).\n if (picked === null || picked === undefined || picked === '') return;\n if (multiple) {\n onToggle?.(picked as string | number, row as Record<string, unknown>);\n return;\n }\n onSelect(picked as string | number);\n onSelectRow?.(row as Record<string, unknown>);\n onOpenChange(false);\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange} modal={create?.pending !== true}>\n <DialogContent\n // While a create of this select is open its sheet stacks above this dialog and stays\n // interactive: the dialog neither traps focus nor closes on a click outside, and Escape\n // closes only the sheet (P6-67).\n {...(create?.pending === true\n ? {\n onInteractOutside: (event: Event) => event.preventDefault(),\n onEscapeKeyDown: (event: KeyboardEvent) => event.preventDefault(),\n }\n : {})}\n className=\"flex max-h-[85vh] w-[calc(100vw-1.5rem)] max-w-3xl flex-col gap-0 overflow-hidden p-0\"\n data-testid={`relation-picker-dialog:${to}`}\n >\n <DialogHeader className=\"border-b px-4 py-3\">\n <DialogTitle className=\"text-base\">\n {tr('relationPicker.title', { name: targetLabel }, `Select: ${targetLabel}`)}\n </DialogTitle>\n <DialogDescription className=\"sr-only\">\n {tr('relationPicker.advancedSearch', undefined, 'Advanced search')}\n </DialogDescription>\n </DialogHeader>\n\n {/* Search + the filter builder (#9 / Task 5 SP3) */}\n <div className=\"flex flex-wrap items-center gap-2 border-b px-4 py-2.5\">\n {filterState.searchEnabled && (\n <div className=\"relative min-w-[12rem] flex-1\">\n <IconSearch className=\"pointer-events-none absolute left-2.5 top-2.5 size-4 text-muted-foreground\" />\n <Input\n autoFocus\n value={search}\n onChange={(e) => setSearchInput(e.target.value)}\n placeholder={tr('toolbar.search', { name: targetLabel }, `Search ${targetLabel}…`)}\n data-testid={`relation-picker-dialog:${to}:search`}\n className=\"h-9 pl-8\"\n />\n </div>\n )}\n {/*\n No `pinnedFields`/`pinnedValue`/`setPinnedValue` — this dialog has\n no pinned slot (`useDialogFilterState`'s doc: a modal has no\n spare real estate for always-visible facets), so `FilterBuilder`\n falls back to its own stable empty-array defaults.\n */}\n <FilterBuilder\n group={filterState.group}\n setGroup={filterState.setGroup}\n available={filterState.available}\n fixed={filterState.fixed}\n setPage={filterState.setPage}\n testIdBase={`relation-picker-dialog:${to}:filter`}\n />\n </div>\n\n {/* Fixed-height table area — constant height so paging never jumps. */}\n <div className=\"h-[46vh] min-h-[16rem] overflow-auto\">\n <Table>\n <TableHeader className=\"sticky top-0 z-10 bg-background shadow-[inset_0_-1px_0_var(--border)]\">\n <TableRow className=\"hover:bg-transparent\">\n <TableHead\n className=\"h-9 w-14 cursor-pointer select-none\"\n onClick={() => toggleSort('id')}\n >\n {tr('relationPicker.idColumn', undefined, 'ID')}{' '}\n <SortIcon field=\"id\" sort={sort} />\n </TableHead>\n <TableHead\n className={cn('h-9 select-none', titleSortable && 'cursor-pointer')}\n {...(titleSortable ? { onClick: () => toggleSort(displayField) } : {})}\n >\n {targetLabel} {titleSortable && <SortIcon field={displayField} sort={sort} />}\n </TableHead>\n {extraColumns.map((c) => (\n <TableHead\n key={c}\n className=\"h-9 cursor-pointer select-none\"\n onClick={() => toggleSort(c)}\n >\n {resolveFieldLabel(\n targetCaps,\n c,\n declaredText('field', targetDef?.fields?.[c], 'label'),\n )}\n <SortIcon field={c} sort={sort} />\n </TableHead>\n ))}\n <TableHead className=\"h-9 w-8\" />\n </TableRow>\n </TableHeader>\n <TableBody>\n {isLoading ? (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell colSpan={colCount + 1} className=\"h-64 text-center\">\n <IconLoader2 className=\"mx-auto size-4 animate-spin text-muted-foreground\" />\n </TableCell>\n </TableRow>\n ) : rows.length === 0 ? (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell\n colSpan={colCount + 1}\n className=\"h-64 text-center text-sm text-muted-foreground\"\n >\n {tr('relation.noResults', undefined, 'No results.')}\n </TableCell>\n </TableRow>\n ) : (\n rows.map((row) => {\n const idStr = String(recordKeyOf(targetDef, row));\n const rowDisabled = isDisabled(row);\n // A disabled row is drawn checked: it is already chosen elsewhere.\n const checked =\n rowDisabled ||\n (selected ?? []).some((key) => String(key) === String(keyOf(row)));\n return (\n <TableRow\n key={idStr}\n className={cn(\n rowDisabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer',\n )}\n data-testid={`relation-picker-dialog:${to}:row:${idStr}`}\n {...(multiple ? { 'aria-selected': checked } : {})}\n {...(rowDisabled ? { 'aria-disabled': true } : {})}\n onClick={() => pick(row)}\n >\n <TableCell className=\"py-2 tabular-nums text-muted-foreground\">\n {tr('relationPicker.recordNumber', { id: idStr }, `#${idStr}`)}\n </TableCell>\n <TableCell className=\"py-2 font-medium\">{rowTitle(row)}</TableCell>\n {extraColumns.map((c) => (\n <TableCell key={c} className=\"py-2 text-muted-foreground\">\n {/* The list's display renderers: locale numbers and dates, enum labels. */}\n <ResolvedFieldDisplay\n resource={targetDef?.ref ?? { namespace: 'default', id: to }}\n name={c}\n field={\n (targetDef === undefined\n ? undefined\n : declaredField(targetDef as unknown as DtoSource, c)) ??\n (targetDef?.fields?.[c] as never) ?? { type: 'unknown' }\n }\n value={row[c]}\n record={row}\n fallback={formatCell(row[c], targetDef?.fields?.[c], (ns) =>\n app.registry.enums.get(ns),\n )}\n />\n </TableCell>\n ))}\n <TableCell className=\"py-2 text-right\">\n {multiple ? (\n <IconCheck\n className={cn('ml-auto size-4', checked ? 'opacity-100' : 'opacity-0')}\n aria-hidden\n />\n ) : (\n <IconChevronRight className=\"ml-auto size-4 text-muted-foreground/50\" />\n )}\n </TableCell>\n </TableRow>\n );\n })\n )}\n </TableBody>\n </Table>\n </div>\n\n <DialogFooter className=\"flex-row items-center justify-between gap-2 border-t px-4 py-2.5 sm:justify-between\">\n {/*\n A wrapper that is always there: the footer is `justify-between`, so dropping its first\n child (no create offered) would slide the pager to the left edge.\n */}\n <div className=\"flex items-center gap-3\">\n {checking && (\n <span\n role=\"status\"\n className=\"inline-flex items-center gap-1.5 text-sm text-muted-foreground\"\n data-testid={`relation-picker-dialog:${to}:disabled-checking`}\n >\n <IconLoader2 className=\"size-3.5 animate-spin\" aria-hidden=\"true\" />\n {tr('relationPicker.checking', undefined, 'Checking these rows…')}\n </span>\n )}\n {checkFailed && (\n <span\n role=\"alert\"\n className=\"inline-flex items-center gap-2 text-sm text-destructive\"\n data-testid={`relation-picker-dialog:${to}:disabled-failed`}\n >\n {tr(\n 'relationPicker.checkFailed',\n undefined,\n 'These rows could not be checked, so none can be chosen.',\n )}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7\"\n onClick={() => setAttempt((n) => n + 1)}\n data-testid={`relation-picker-dialog:${to}:disabled-retry`}\n >\n {chromeT('actions.retry', { defaultValue: 'Retry' })}\n </Button>\n </span>\n )}\n {disabledCount > 0 && disabledLabel !== undefined && (\n <span\n className=\"text-sm text-muted-foreground\"\n data-testid={`relation-picker-dialog:${to}:disabled-count`}\n >\n {disabledLabel(disabledCount)}\n </span>\n )}\n {create !== undefined && (\n <RelationCreateButton\n to={to}\n fieldName={fieldName ?? to}\n create={create}\n seeds={createSeeds}\n search={search}\n beforeOpen={onBeforeCreate ?? noop}\n className=\"inline-flex items-center gap-1 whitespace-nowrap text-sm text-foreground hover:underline disabled:opacity-50\"\n />\n )}\n </div>\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <span className=\"hidden tabular-nums sm:inline\">\n {knownTotal\n ? tr(\n 'pagination.pageOf',\n { page: filterState.page, total: totalPages },\n `${filterState.page} / ${totalPages}`,\n )\n : tr('pagination.pageOnly', { page: filterState.page }, `${filterState.page}`)}\n </span>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n className=\"h-8\"\n disabled={filterState.page <= 1}\n onClick={() => filterState.setPage(Math.max(1, filterState.page - 1))}\n data-testid={`relation-picker-dialog:${to}:prev`}\n >\n {tr('relationPicker.prev', undefined, 'Prev')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n className=\"h-8\"\n disabled={!hasNextPage}\n onClick={() => filterState.setPage(filterState.page + 1)}\n data-testid={`relation-picker-dialog:${to}:next`}\n >\n {tr('relationPicker.next', undefined, 'Next')}\n </Button>\n {multiple && (\n <Button\n type=\"button\"\n size=\"sm\"\n className=\"h-8\"\n onClick={() => (onDone === undefined ? onOpenChange(false) : onDone())}\n data-testid={`relation-picker-dialog:${to}:done`}\n >\n {tr('relationPicker.select', undefined, 'Select')}\n </Button>\n )}\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n/**\n * A column's sort arrow. Declared here, not inside the dialog: a component made in a render is a\n * new type on every render, so React replaced these icons each time the dialog rendered — and a\n * node removed from the dialog while focus was crossing into the filter popover (which is portaled\n * outside it, so focus sits on `body` for that moment) makes the dialog's focus scope take focus\n * back, which closed the popover on any click inside it.\n */\nfunction SortIcon({\n field,\n sort,\n}: {\n readonly field: string;\n readonly sort: { readonly field: string; readonly dir: 'asc' | 'desc' };\n}): React.ReactElement {\n if (sort.field !== field) return <IconSelector className=\"ml-1 inline size-3 opacity-40\" />;\n return sort.dir === 'asc' ? (\n <IconArrowUp className=\"ml-1 inline size-3\" />\n ) : (\n <IconArrowDown className=\"ml-1 inline size-3\" />\n );\n}\n\nfunction formatCell(\n value: unknown,\n field: unknown,\n enumOf: (namespace: string) => Parameters<typeof enumValueText>[0],\n): string {\n if (value == null || value === '') return '—';\n if (typeof value === 'boolean') return value ? '✓' : '—';\n // Map an option value → its human label: the field's `texts.options`, the bound enum's\n // `texts`, else a label the field's `meta.options` carries (adopter data).\n const namespace =\n field !== null && typeof field === 'object'\n ? enumNamespaceOf(field as Parameters<typeof enumNamespaceOf>[0])\n : undefined;\n const declared =\n fieldOptionText(field, String(value)) ??\n (namespace === undefined ? undefined : enumValueText(enumOf(namespace), String(value)));\n if (declared !== undefined) return declared;\n const options = (field as { meta?: { options?: EnumOption[] } } | undefined)?.meta?.options;\n if (Array.isArray(options)) {\n const hit = options.find((o) => o.value === String(value));\n if (hit && typeof hit.label === 'string') return hit.label;\n }\n const s = String(value);\n return s.length > 40 ? `${s.slice(0, 40)}…` : s;\n}\n"],"mappings":"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"}
|