@burojs/ui-shadcn 0.5.0 → 0.6.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/candidates-gate-Bb70LPAy.js +135 -0
- package/dist/candidates-gate-Bb70LPAy.js.map +1 -0
- package/dist/candidates-gate-D33jXLHO.cjs +153 -0
- package/dist/candidates-gate-D33jXLHO.cjs.map +1 -0
- package/dist/candidates-relation-picker-B116Bmqi.js +31 -0
- package/dist/candidates-relation-picker-B116Bmqi.js.map +1 -0
- package/dist/candidates-relation-picker-Cy0rTxvI.cjs +31 -0
- package/dist/candidates-relation-picker-Cy0rTxvI.cjs.map +1 -0
- package/dist/{check-confirm-dialog-BRByTFpg.cjs → check-confirm-dialog--gPFnEeg.cjs} +27 -14
- package/dist/check-confirm-dialog--gPFnEeg.cjs.map +1 -0
- package/dist/{check-confirm-dialog-CkaVTPT9.js → check-confirm-dialog-bRT8lt0d.js} +28 -15
- package/dist/check-confirm-dialog-bRT8lt0d.js.map +1 -0
- package/dist/{filter-builder-DOnaXNfP.js → filter-builder-Bhn-dhZV.js} +30 -14
- package/dist/filter-builder-Bhn-dhZV.js.map +1 -0
- package/dist/{filter-builder-B3mZFL-K.cjs → filter-builder-nXqtM_h4.cjs} +39 -11
- package/dist/filter-builder-nXqtM_h4.cjs.map +1 -0
- package/dist/{flow-runtime-B6zOHYqI.cjs → flow-runtime-D3avVH0I.cjs} +3 -3
- package/dist/{flow-runtime-B6zOHYqI.cjs.map → flow-runtime-D3avVH0I.cjs.map} +1 -1
- package/dist/{flow-runtime-D8h99FSg.js → flow-runtime-s6lkRPKF.js} +3 -3
- package/dist/{flow-runtime-D8h99FSg.js.map → flow-runtime-s6lkRPKF.js.map} +1 -1
- package/dist/{flow-screen-CmbARSqg.js → flow-screen-CCvgDI9G.js} +11 -8
- package/dist/flow-screen-CCvgDI9G.js.map +1 -0
- package/dist/{flow-screen-CX05b3G9.cjs → flow-screen-Ci_Sik4Y.cjs} +10 -7
- package/dist/flow-screen-Ci_Sik4Y.cjs.map +1 -0
- package/dist/index.cjs +577 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +181 -103
- package/dist/index.d.ts +181 -103
- package/dist/index.js +567 -45
- package/dist/index.js.map +1 -1
- package/dist/{inline-flow-host-C3H2UpSA.js → inline-flow-host-B6FI0ctQ.js} +3 -3
- package/dist/{inline-flow-host-C3H2UpSA.js.map → inline-flow-host-B6FI0ctQ.js.map} +1 -1
- package/dist/{inline-flow-host-B7U1OJUe.cjs → inline-flow-host-DV1et3c6.cjs} +3 -3
- package/dist/{inline-flow-host-B7U1OJUe.cjs.map → inline-flow-host-DV1et3c6.cjs.map} +1 -1
- package/dist/label-66W1qbke.js.map +1 -1
- package/dist/label-w6JYf2cF.cjs.map +1 -1
- package/dist/markdown/index.d.cts +2 -3
- package/dist/markdown/index.d.ts +2 -3
- package/dist/{page-flow-route-CQ7HsbBm.cjs → page-flow-route-hdnULWqd.cjs} +6 -6
- package/dist/{page-flow-route-CQ7HsbBm.cjs.map → page-flow-route-hdnULWqd.cjs.map} +1 -1
- package/dist/{page-flow-route-CyXSvXSE.js → page-flow-route-i3ZfJ1sd.js} +6 -6
- package/dist/{page-flow-route-CyXSvXSE.js.map → page-flow-route-i3ZfJ1sd.js.map} +1 -1
- package/dist/{place-node-SIp4XYQH.cjs → place-node-C5SmIAYU.cjs} +441 -135
- package/dist/place-node-C5SmIAYU.cjs.map +1 -0
- package/dist/{place-node--GdsP688.js → place-node-NraNYKPx.js} +420 -138
- package/dist/place-node-NraNYKPx.js.map +1 -0
- package/dist/{related-list-cards-B6x9HQQp.js → related-list-cards-BWZ9GfOG.js} +3 -3
- package/dist/{related-list-cards-B6x9HQQp.js.map → related-list-cards-BWZ9GfOG.js.map} +1 -1
- package/dist/{related-list-cards-nxuODlIA.cjs → related-list-cards-CFq3meUn.cjs} +3 -3
- package/dist/{related-list-cards-nxuODlIA.cjs.map → related-list-cards-CFq3meUn.cjs.map} +1 -1
- package/dist/{related-list-timeline-BYJKEbmA.cjs → related-list-timeline-CCzl71aA.cjs} +2 -2
- package/dist/{related-list-timeline-BYJKEbmA.cjs.map → related-list-timeline-CCzl71aA.cjs.map} +1 -1
- package/dist/{related-list-timeline-DEdfd-rr.js → related-list-timeline-yrPsjA3F.js} +2 -2
- package/dist/{related-list-timeline-DEdfd-rr.js.map → related-list-timeline-yrPsjA3F.js.map} +1 -1
- package/dist/{related-rows-zh5f_4QR.cjs → related-rows-ea43qhhy.cjs} +10 -11
- package/dist/related-rows-ea43qhhy.cjs.map +1 -0
- package/dist/{related-rows-B4levHcR.js → related-rows-o0_t-Yce.js} +7 -8
- package/dist/related-rows-o0_t-Yce.js.map +1 -0
- package/dist/{relation-create-BOaUe12G.cjs → relation-create-IA40t1hi.cjs} +10 -8
- package/dist/relation-create-IA40t1hi.cjs.map +1 -0
- package/dist/{relation-create-Bg-eEJLs.js → relation-create-h6F9B4C4.js} +10 -8
- package/dist/relation-create-h6F9B4C4.js.map +1 -0
- package/dist/{relation-many-picker-B-NCEUgX.cjs → relation-many-picker-C8zFEjwd.cjs} +14 -12
- package/dist/relation-many-picker-C8zFEjwd.cjs.map +1 -0
- package/dist/{relation-many-picker-gdAOD-qK.js → relation-many-picker-bvkjkv-k.js} +13 -11
- package/dist/relation-many-picker-bvkjkv-k.js.map +1 -0
- package/dist/{relation-picker-BAY77ivO.js → relation-picker-Z0ZJJ5uS.js} +68 -37
- package/dist/relation-picker-Z0ZJJ5uS.js.map +1 -0
- package/dist/{relation-picker-dialog-M0pQaWlp.cjs → relation-picker-dialog-0IzkjmiA.cjs} +17 -13
- package/dist/relation-picker-dialog-0IzkjmiA.cjs.map +1 -0
- package/dist/{relation-picker-dialog-hA9rYrZB.js → relation-picker-dialog-B6nIiJBf.js} +18 -14
- package/dist/relation-picker-dialog-B6nIiJBf.js.map +1 -0
- package/dist/{relation-picker-Da_uhFvA.cjs → relation-picker-gag0qSR5.cjs} +74 -37
- package/dist/relation-picker-gag0qSR5.cjs.map +1 -0
- package/dist/{resource-page-router-T4vS5gbJ.js → resource-page-router-3RUf9d1j.js} +44 -38
- package/dist/resource-page-router-3RUf9d1j.js.map +1 -0
- package/dist/{resource-page-router-OSQyRA5b.cjs → resource-page-router-PXfMW9nh.cjs} +42 -36
- package/dist/resource-page-router-PXfMW9nh.cjs.map +1 -0
- package/dist/{search-access-CURsWn-U.js → search-access-CPPmfuvy.js} +4 -19
- package/dist/search-access-CPPmfuvy.js.map +1 -0
- package/dist/{search-access-B7G5oR8n.cjs → search-access-Dugvttlg.cjs} +3 -24
- package/dist/search-access-Dugvttlg.cjs.map +1 -0
- package/dist/{toaster-CfCRLgR-.js → toaster-C02XbB9r.js} +30 -59
- package/dist/toaster-C02XbB9r.js.map +1 -0
- package/dist/{toaster-C7CiYA4Z.cjs → toaster-Cr51X7bz.cjs} +30 -83
- package/dist/toaster-Cr51X7bz.cjs.map +1 -0
- package/dist/{use-dialog-filter-state-BDcgS5Oz.js → use-dialog-filter-state-C--oMAZG.js} +2 -2
- package/dist/{use-dialog-filter-state-BDcgS5Oz.js.map → use-dialog-filter-state-C--oMAZG.js.map} +1 -1
- package/dist/{use-dialog-filter-state-BBCC0OEK.cjs → use-dialog-filter-state-Dhe8PcB5.cjs} +2 -2
- package/dist/{use-dialog-filter-state-BBCC0OEK.cjs.map → use-dialog-filter-state-Dhe8PcB5.cjs.map} +1 -1
- package/dist/use-theme-DFlpXils.cjs.map +1 -1
- package/dist/use-theme-dnE3Od3I.js.map +1 -1
- package/package.json +9 -9
- package/dist/candidates-gate-CX2PBu6l.js +0 -63
- package/dist/candidates-gate-CX2PBu6l.js.map +0 -1
- package/dist/candidates-gate-DiFI52r7.cjs +0 -70
- package/dist/candidates-gate-DiFI52r7.cjs.map +0 -1
- package/dist/candidates-relation-picker-CBsVcnPR.js +0 -82
- package/dist/candidates-relation-picker-CBsVcnPR.js.map +0 -1
- package/dist/candidates-relation-picker-Dd_GVLlu.cjs +0 -84
- package/dist/candidates-relation-picker-Dd_GVLlu.cjs.map +0 -1
- package/dist/check-confirm-dialog-BRByTFpg.cjs.map +0 -1
- package/dist/check-confirm-dialog-CkaVTPT9.js.map +0 -1
- package/dist/filter-builder-B3mZFL-K.cjs.map +0 -1
- package/dist/filter-builder-DOnaXNfP.js.map +0 -1
- package/dist/flow-screen-CX05b3G9.cjs.map +0 -1
- package/dist/flow-screen-CmbARSqg.js.map +0 -1
- package/dist/lazy-relation-picker-dialog-2oxnA6IR.js +0 -14
- package/dist/lazy-relation-picker-dialog-2oxnA6IR.js.map +0 -1
- package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs +0 -19
- package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs.map +0 -1
- package/dist/peek-filter-CnqHwTMU.cjs +0 -51
- package/dist/peek-filter-CnqHwTMU.cjs.map +0 -1
- package/dist/peek-filter-ErLfO153.js +0 -28
- package/dist/peek-filter-ErLfO153.js.map +0 -1
- package/dist/place-node--GdsP688.js.map +0 -1
- package/dist/place-node-SIp4XYQH.cjs.map +0 -1
- package/dist/related-rows-B4levHcR.js.map +0 -1
- package/dist/related-rows-zh5f_4QR.cjs.map +0 -1
- package/dist/relation-create-BOaUe12G.cjs.map +0 -1
- package/dist/relation-create-Bg-eEJLs.js.map +0 -1
- package/dist/relation-many-picker-B-NCEUgX.cjs.map +0 -1
- package/dist/relation-many-picker-gdAOD-qK.js.map +0 -1
- package/dist/relation-picker-BAY77ivO.js.map +0 -1
- package/dist/relation-picker-Da_uhFvA.cjs.map +0 -1
- package/dist/relation-picker-dialog-M0pQaWlp.cjs.map +0 -1
- package/dist/relation-picker-dialog-hA9rYrZB.js.map +0 -1
- package/dist/resource-page-router-OSQyRA5b.cjs.map +0 -1
- package/dist/resource-page-router-T4vS5gbJ.js.map +0 -1
- package/dist/search-access-B7G5oR8n.cjs.map +0 -1
- package/dist/search-access-CURsWn-U.js.map +0 -1
- package/dist/toaster-C7CiYA4Z.cjs.map +0 -1
- package/dist/toaster-CfCRLgR-.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relation-create-h6F9B4C4.js","names":[],"sources":["../src/fields/relation-hand-back.tsx","../src/fields/relation-view-button.tsx","../src/fields/relation-create.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport {\n composeListFilter,\n type ErasedResourceHandle,\n type FilterExpression,\n type FixedFilterInput,\n normalizeFixedFilters,\n type ScopeSource,\n type SearchDecl,\n} from '@burojs/core';\nimport { useList } from '@burojs/react';\n\ntype ErasedExpression = FilterExpression<ErasedResourceHandle>;\n\n/**\n * The filter the select re-reads a created record through (P6-33): the picker's own option query\n * for an empty search (`composeListFilter`, the same `fixed` filters, the same search\n * declaration), conjoined with `<idField> eq <id>`. The row must come back through the query the\n * select offers from, or it is not selected.\n */\nexport function createdOptionFilter(input: {\n readonly fixed: readonly FixedFilterInput[] | undefined;\n readonly idField: string;\n readonly id: string | number;\n readonly searchDecl?: SearchDecl | undefined;\n readonly searchContext?: { readonly resource: string; readonly def: unknown };\n}): ErasedExpression {\n const own = createdOptionScope(input);\n const byId = { field: input.idField, op: 'eq', value: input.id } as ErasedExpression;\n return own === undefined ? byId : ({ and: [own, byId] } as ErasedExpression);\n}\n\n/**\n * The select's option query for an empty search, without the id predicate: what the picker itself\n * sends, so the same hidden-field rule (`namesHiddenField`) judges both.\n */\nexport function createdOptionScope(input: {\n readonly fixed: readonly FixedFilterInput[] | undefined;\n readonly searchDecl?: SearchDecl | undefined;\n readonly searchContext?: { readonly resource: string; readonly def: unknown };\n}): ErasedExpression | undefined {\n return composeListFilter({\n fixed: normalizeFixedFilters(input.fixed),\n search: '',\n ...(input.searchDecl === undefined ? {} : { searchDecl: input.searchDecl }),\n ...(input.searchContext === undefined ? {} : { searchContext: input.searchContext as never }),\n });\n}\n\n/** What the re-read settled on: the value to select, \"does not fit\", or a failed read. */\nexport type CreatedOptionResult =\n | { readonly kind: 'fits'; readonly value: string | number }\n | { readonly kind: 'does-not-fit' }\n | { readonly kind: 'failed' };\n\nexport interface CreatedOptionReadProps {\n readonly to: string;\n readonly filter: ErasedExpression;\n /**\n * The select's read scope, as its option list takes it: the picker and the hand-back never\n * disagree on which rows fit.\n */\n readonly scope: ScopeSource;\n /** The record field the select writes (`useList`'s `scopeField`), as its option list passes it. */\n readonly scopeField?: string;\n readonly by: string | undefined;\n readonly idField: string;\n /** Called once, with the settled answer. */\n readonly onSettled: (result: CreatedOptionResult) => void;\n}\n\n/**\n * Mounted only while a created record is being handed back. One list read, limit 1, through the\n * select's own option query (Known finding 13): an empty answer is \"does not fit\", a failed read\n * is a failure, never \"fits\". Mounting it is the request; unmounting it drops the answer.\n */\nexport function CreatedOptionRead({\n to,\n filter,\n scope,\n scopeField,\n by,\n idField,\n onSettled,\n}: CreatedOptionReadProps): null {\n const query = useList(to, {\n filter: filter as never,\n pagination: { kind: 'offset', page: 1, pageSize: 1 },\n scope,\n ...(scopeField === undefined ? {} : { scopeField }),\n retry: false,\n });\n const settled = React.useRef(false);\n const latest = React.useRef(onSettled);\n latest.current = onSettled;\n const rows = (query.data as { readonly data?: readonly Record<string, unknown>[] } | undefined)\n ?.data;\n const answered = rows !== undefined && query.isCurrentData && !query.isFetching;\n const failed = query.isError && !query.isFetching;\n React.useEffect(() => {\n if (settled.current) return;\n if (failed) {\n settled.current = true;\n latest.current({ kind: 'failed' });\n return;\n }\n if (!answered) return;\n settled.current = true;\n const value = rows[0]?.[by ?? idField];\n latest.current(\n typeof value === 'string' || typeof value === 'number'\n ? { kind: 'fits', value }\n : { kind: 'does-not-fit' },\n );\n }, [answered, failed, rows, by, idField]);\n return null;\n}\n","'use client';\n\nimport * as React from 'react';\nimport { IconEye } from '@tabler/icons-react';\nimport { type RelationOpen, resourceShowHref } from '@burojs/core';\nimport {\n QUICK_ANCHOR_ATTRIBUTE,\n useApp,\n useEntryGate,\n useList,\n useListAccess,\n useOptionalT,\n useQuick,\n useRouteNavigate,\n useTestIds,\n} from '@burojs/react';\nimport { buttonVariants } from '../primitives/button.js';\nimport { cn } from '../lib/cn.js';\nimport { useLinkComponent } from '../layout/link-component.js';\nimport { ClaimedQuickLink, isPlainLeftClick } from './claimed-quick-link.js';\nimport { linksToPage } from './relation-display.js';\n\n/** A key a read may send: a non-empty string or a finite number (Known finding 1). */\nexport function selectKey(value: unknown): string | null {\n if (typeof value === 'string') return value.length === 0 ? null : value;\n if (typeof value === 'number') return Number.isFinite(value) ? String(value) : null;\n return null;\n}\n\nconst VIEW_CLASS = cn(buttonVariants({ variant: 'outline', size: 'icon' }), 'shrink-0');\n\nexport interface RelationViewButtonProps {\n readonly to: string;\n readonly value: unknown;\n readonly by: string | undefined;\n /** The form field holding the key: the test id's second part. */\n readonly fieldName: string;\n /** Runs before the overlay opens: the picker moves focus to its trigger, where it returns. */\n readonly beforeOpen: () => void;\n /**\n * The field's `ui.open` (P7 3.6): `quick` (default) opens the record above the form, `page`\n * goes to its show page through the navigation guard (a dirty form asks first), `none` draws\n * no button.\n */\n readonly open?: RelationOpen;\n}\n\n/**\n * \"View\" beside a relation select's trigger (P6-28): the target's `quick.show` as a new overlay\n * above the form, never a push and never closing the host. Drawn only when the select holds a\n * key and the entry gate is open without a row (the row-level re-check runs inside the overlay,\n * P3-44); while the gate is undecided nothing is drawn and nothing is read (P3-15). A `by` key is\n * resolved by the limit-2 read on click; zero or two rows open nothing.\n */\nexport function RelationViewButton(props: RelationViewButtonProps): React.ReactElement | null {\n const gate = useEntryGate();\n const { to, value, by } = props;\n const access = useListAccess(to);\n const key = selectKey(value);\n if (key === null || props.open === 'none') return null;\n // A `by` field the user may not view is never sent as a predicate: no \"view\", no read.\n if (by !== undefined && !(access.status === 'ready' && access.fieldVisible(by))) return null;\n const answer = gate(to, 'show');\n if (answer.state !== 'open') return null;\n // Keyed by the value: a click asked for one key's read and must not outlive it.\n return <ViewButton key={key} {...props} keyString={key} />;\n}\n\nfunction ViewButton({\n to,\n by,\n fieldName,\n keyString,\n beforeOpen,\n open: openMode = 'quick',\n}: RelationViewButtonProps & { readonly keyString: string }): React.ReactElement | null {\n const app = useApp();\n const t = useOptionalT();\n const tid = useTestIds();\n const quick = useQuick();\n const navigate = useRouteNavigate();\n const RouterLink = useLinkComponent();\n const [asked, setAsked] = React.useState(false);\n const requested = React.useRef(false);\n const targetDef = app.registry.get(to);\n const rawIdField = (targetDef?.data as { readonly idField?: unknown } | undefined)?.idField;\n const idField = typeof rawIdField === 'string' && rawIdField.length > 0 ? rawIdField : 'id';\n const read = useList(to, {\n ...(asked && by !== undefined\n ? {\n filter: { field: by, op: 'eq', value: keyString } as never,\n pagination: { kind: 'offset', page: 1, pageSize: 2 } as never,\n }\n : {}),\n // A key names one record: the read is never narrowed by a scope.\n scope: 'none',\n enabled: asked && by !== undefined,\n retry: false,\n });\n // Rows of this key only: a placeholder carried over from another key's read is not an answer.\n const current = read.isCurrentData && !read.isPlaceholderData && !read.isFetching;\n const rows = current\n ? (read.data as { readonly data?: readonly Record<string, unknown>[] } | undefined)?.data\n : undefined;\n const open = React.useCallback(\n (id: string) => {\n beforeOpen();\n if (openMode === 'page') {\n // The page of the record, through the navigation guard: unsaved input asks first.\n if (targetDef !== undefined && linksToPage(app, targetDef))\n navigate(resourceShowHref(app.registry, targetDef, to, id));\n return;\n }\n quick.show(to, id);\n },\n [beforeOpen, quick, to, openMode, navigate, app, targetDef],\n );\n // Only a click opens: the read answers the click that asked for it, once.\n React.useEffect(() => {\n if (by === undefined || !asked || rows === undefined || !requested.current) return;\n requested.current = false;\n if (rows.length !== 1) {\n if (rows.length > 1 && process.env.NODE_ENV !== 'production')\n console.error(\n `[burojs] ${to}: more than one row has ${by} ${keyString}; \"view\" opened nothing.`,\n );\n return;\n }\n const id = rows[0]?.[idField];\n const next = selectKey(id);\n if (next !== null) open(next);\n }, [by, asked, rows, idField, keyString, to, open]);\n\n const label = t('relationPicker.view', { defaultValue: 'View' });\n const icon = <IconEye className=\"h-4 w-4\" aria-hidden />;\n const testid = tid.relationView(to, fieldName);\n // `page` needs a page to go to (the publish check says so): without one, nothing is drawn.\n if (openMode === 'page' && (targetDef === undefined || !linksToPage(app, targetDef))) return null;\n if (openMode === 'page' && by === undefined && targetDef !== undefined)\n return (\n <RouterLink\n href={resourceShowHref(app.registry, targetDef, to, keyString)}\n data-testid={testid}\n className={VIEW_CLASS}\n onClick={() => beforeOpen()}\n >\n {icon}\n <span className=\"sr-only\">{label}</span>\n </RouterLink>\n );\n if (by === undefined && targetDef !== undefined && linksToPage(app, targetDef))\n return (\n <ClaimedQuickLink\n href={resourceShowHref(app.registry, targetDef, to, keyString)}\n testid={testid}\n className={VIEW_CLASS}\n onOpen={() => open(keyString)}\n >\n {icon}\n <span className=\"sr-only\">{label}</span>\n </ClaimedQuickLink>\n );\n return (\n <button\n type=\"button\"\n className={VIEW_CLASS}\n data-testid={testid}\n aria-label={label}\n {...{ [QUICK_ANCHOR_ATTRIBUTE]: '' }}\n onClick={(event) => {\n event.preventDefault();\n event.stopPropagation();\n // No page to follow (or, for a `by` key, no id to build it from): a modified click is\n // nothing, never an overlay the user asked to avoid.\n if (!isPlainLeftClick(event)) return;\n if (by === undefined) {\n open(keyString);\n return;\n }\n requested.current = true;\n setAsked(true);\n // A key already read opens at once; the effect answers a click made before the read.\n if (rows !== undefined && rows.length === 1) {\n const next = selectKey(rows[0]?.[idField]);\n requested.current = false;\n if (next !== null) open(next);\n }\n }}\n >\n {icon}\n </button>\n );\n}\n","'use client';\n\nimport * as React from 'react';\nimport type { FixedFilterInput, ScopeSource, SearchDecl } from '@burojs/core';\nimport {\n type QuickSettledRow,\n useApp,\n useListAccess,\n useEntryGate,\n useOptionalT,\n useQuick,\n useTestIds,\n} from '@burojs/react';\nimport { toast } from '../notification/toaster.js';\nimport {\n CreatedOptionRead,\n type CreatedOptionResult,\n createdOptionFilter,\n createdOptionScope,\n} from './relation-hand-back.js';\nimport { namesHiddenField } from './search-access.js';\nimport { selectKey } from './relation-view-button.js';\n\n/** What the picker and its dialog share: one pending create per select (P6-34). */\nexport interface SelectCreate {\n readonly pending: boolean;\n /**\n * The hand-back read is forbidden: its filter names a field P5 hides from the user (the same\n * rule that stops the picker's own option read), or the access map is not ready. A blocked\n * select offers no \"+ Create\" and starts none, so no predicate on a hidden field is ever sent.\n */\n readonly blocked: boolean;\n /** Runs `open` unless a create of this select is already pending or the select is gone. */\n start(open: () => Promise<QuickSettledRow>): void;\n}\n\ninterface SelectCreateInput {\n readonly to: string;\n readonly by: string | undefined;\n /** The row field the select holds (`by`, else the target's id field). */\n readonly valueField: string;\n readonly idField: string;\n readonly fixedFilters: readonly FixedFilterInput[] | undefined;\n /** The select's read scope: the hand-back reads as the option list does. */\n readonly scope: ScopeSource;\n /** The record field the select writes, as the option list passes it. */\n readonly scopeField?: string;\n /**\n * The select's candidates gate is not ready (pending, empty, forbidden): the filters above\n * lack the candidate filters, so the hand-back must not read.\n */\n readonly held?: boolean;\n readonly searchDecl: SearchDecl | undefined;\n readonly searchContext: { readonly resource: string; readonly def: unknown };\n /** Called with the value to select, as a user's pick would. */\n readonly onValue: (value: string | number) => void;\n}\n\n/**\n * The select's one pending create: the latch (a ref, so a double click or a second render cannot\n * start two), the mounted guard (a result arriving after the select unmounted is ignored — the\n * record exists, nothing is selected), and the hand-back re-read through the select's own option\n * query. `reader` is mounted by the select while a created record is being handed back.\n */\nexport function useSelectCreate(input: SelectCreateInput): {\n readonly create: SelectCreate;\n readonly reader: React.ReactNode;\n} {\n const t = useOptionalT();\n const { to: target } = input;\n const [pending, setPending] = React.useState(false);\n const [created, setCreated] = React.useState<{ readonly id: string } | null>(null);\n const latch = React.useRef(false);\n const mounted = React.useRef(true);\n React.useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n const latest = React.useRef(input);\n latest.current = input;\n const access = useListAccess(target);\n // The hand-back's own filter, judged as the picker judges its option query.\n const blocked =\n input.held === true ||\n access.status !== 'ready' ||\n namesHiddenField(\n createdOptionScope({\n fixed: input.fixedFilters,\n searchDecl: input.searchDecl,\n searchContext: input.searchContext,\n }),\n (name) => access.fieldVisible(name),\n );\n const blockedRef = React.useRef(blocked);\n blockedRef.current = blocked;\n\n const release = React.useCallback(() => {\n latch.current = false;\n if (mounted.current) {\n setPending(false);\n setCreated(null);\n }\n }, []);\n\n const start = React.useCallback(\n (open: () => Promise<QuickSettledRow>) => {\n if (latch.current || !mounted.current || blockedRef.current) return;\n latch.current = true;\n setPending(true);\n let opened: Promise<QuickSettledRow>;\n try {\n opened = open();\n } catch (error) {\n // A synchronous throw (a `beforeOpen`, the host) must not leave the select locked.\n release();\n throw error;\n }\n void opened.then(\n (row) => {\n if (!mounted.current) {\n latch.current = false;\n return;\n }\n const id =\n row === null ? null : (selectKey(row[latest.current.idField]) ?? selectKey(row.id));\n if (id === null) release();\n else setCreated({ id });\n },\n () => release(),\n );\n },\n [release],\n );\n\n const settle = React.useCallback(\n (result: CreatedOptionResult) => {\n if (mounted.current) {\n if (result.kind === 'fits') latest.current.onValue(result.value);\n else if (result.kind === 'does-not-fit')\n toast(\n t('relationPicker.createdNotFitting', {\n defaultValue: 'The record was created but does not fit this field',\n }),\n );\n else\n toast.error(\n t('relationPicker.createdNotSelected', {\n defaultValue: 'The record was created but could not be selected',\n }),\n );\n }\n release();\n },\n [release, t],\n );\n\n const create = React.useMemo<SelectCreate>(\n () => ({ pending, blocked, start }),\n [pending, blocked, start],\n );\n const reader =\n created === null || blocked ? null : (\n <CreatedOptionRead\n key={created.id}\n to={target}\n filter={createdOptionFilter({\n fixed: input.fixedFilters,\n idField: input.idField,\n id: created.id,\n searchDecl: input.searchDecl,\n searchContext: input.searchContext,\n })}\n scope={input.scope}\n {...(input.scopeField === undefined ? {} : { scopeField: input.scopeField })}\n by={input.by}\n idField={input.idField}\n onSettled={settle}\n />\n );\n return { create, reader };\n}\n\n/** A string field a typed search text may fill: declared plain, not system, not an enum. */\nfunction plainStringField(def: unknown): boolean {\n if (def === null || typeof def !== 'object') return false;\n const field = def as {\n readonly kind?: unknown;\n readonly system?: unknown;\n readonly opts?: {\n readonly system?: unknown;\n readonly enum?: unknown;\n readonly localized?: unknown;\n };\n };\n return (\n field.kind === 'string' &&\n field.system !== true &&\n field.opts?.system !== true &&\n field.opts?.enum === undefined &&\n field.opts?.localized !== true\n );\n}\n\nexport interface RelationCreateButtonProps {\n readonly to: string;\n readonly fieldName: string;\n readonly create: SelectCreate;\n /** The trusted `defaults` tier: the registry field's static `eq` literals (P6-31). */\n readonly seeds: Readonly<Record<string, unknown>> | undefined;\n /** The text typed into the select's search, if any: prefills the display field as user input. */\n readonly search?: string;\n /** Runs before the overlay opens: closes the popover, returns focus to the trigger. */\n readonly beforeOpen: () => void;\n readonly className?: string;\n readonly testId?: string;\n}\n\n/**\n * \"+ create\" (P6-29): the target's `quick.create` as a new overlay above the form. Drawn only when\n * the entry gate is open (P3 4.4: `operations.create`, `views.quick.create`, the host wired, the\n * `create` availability); undecided or denied draws nothing and reads nothing. Disabled while one\n * create of the select is pending.\n */\nexport function RelationCreateButton(props: RelationCreateButtonProps): React.ReactElement | null {\n const gate = useEntryGate();\n const { to } = props;\n if (props.create.blocked || gate(to, 'create').state !== 'open') return null;\n return <CreateButton {...props} />;\n}\n\nfunction CreateButton({\n to,\n fieldName,\n create,\n seeds,\n search,\n beforeOpen,\n className,\n testId,\n}: RelationCreateButtonProps): React.ReactElement {\n const app = useApp();\n const t = useOptionalT();\n const tid = useTestIds();\n const quick = useQuick();\n const label = t('relationPicker.create', { defaultValue: '+ Create' });\n return (\n <button\n type=\"button\"\n data-testid={testId ?? tid.relationCreate(to, fieldName)}\n disabled={create.pending}\n className={className}\n onClick={() => {\n const text = search?.trim() ?? '';\n const displayField = (\n app.registry.get(to) as { readonly displayField?: unknown } | undefined\n )?.displayField;\n const fields = (\n app.registry.get(to) as { readonly fields?: Record<string, unknown> } | undefined\n )?.fields;\n const prefill =\n text.length > 0 &&\n typeof displayField === 'string' &&\n fields !== undefined &&\n Object.hasOwn(fields, displayField) &&\n plainStringField(fields[displayField])\n ? { [displayField]: text }\n : undefined;\n create.start(() => {\n beforeOpen();\n return quick.create(to, {\n ...(seeds === undefined || Object.keys(seeds).length === 0 ? {} : { defaults: seeds }),\n ...(prefill === undefined ? {} : { input: prefill }),\n });\n });\n }}\n >\n {label}\n </button>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
const require_use_theme = require("./use-theme-DFlpXils.cjs");
|
|
2
|
-
const require_toaster = require("./toaster-
|
|
2
|
+
const require_toaster = require("./toaster-Cr51X7bz.cjs");
|
|
3
3
|
const require_button = require("./button-BxnnE28H.cjs");
|
|
4
|
-
const require_search_access = require("./search-access-
|
|
5
|
-
const
|
|
6
|
-
const require_relation_create = require("./relation-create-
|
|
7
|
-
const require_candidates_gate = require("./candidates-gate-DiFI52r7.cjs");
|
|
4
|
+
const require_search_access = require("./search-access-Dugvttlg.cjs");
|
|
5
|
+
const require_candidates_gate = require("./candidates-gate-D33jXLHO.cjs");
|
|
6
|
+
const require_relation_create = require("./relation-create-IA40t1hi.cjs");
|
|
8
7
|
let _burojs_core = require("@burojs/core");
|
|
9
8
|
let _burojs_react = require("@burojs/react");
|
|
10
9
|
let _tabler_icons_react = require("@tabler/icons-react");
|
|
@@ -77,7 +76,7 @@ function RelationManyPickerInput(props) {
|
|
|
77
76
|
ariaErrorMessage
|
|
78
77
|
});
|
|
79
78
|
}
|
|
80
|
-
function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, createSeeds, name, id, testid, resource, fieldName, open: openMode, disabled, required, ariaInvalid, ariaDescribedBy, ariaErrorMessage }) {
|
|
79
|
+
function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, createSeeds, name, id, testid, resource, fieldName, open: openMode, scope = "context", disabled, required, ariaInvalid, ariaDescribedBy, ariaErrorMessage }) {
|
|
81
80
|
const app = (0, _burojs_react.useApp)();
|
|
82
81
|
const caps = (0, _burojs_react.useI18nCaps)();
|
|
83
82
|
const t = (0, _burojs_react.useOptionalT)();
|
|
@@ -150,6 +149,8 @@ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, c
|
|
|
150
149
|
valueField,
|
|
151
150
|
idField,
|
|
152
151
|
fixedFilters,
|
|
152
|
+
scope,
|
|
153
|
+
scopeField: fieldName,
|
|
153
154
|
held: gated,
|
|
154
155
|
searchDecl,
|
|
155
156
|
searchContext: {
|
|
@@ -168,7 +169,8 @@ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, c
|
|
|
168
169
|
page: 1,
|
|
169
170
|
pageSize: 10
|
|
170
171
|
},
|
|
171
|
-
|
|
172
|
+
scope,
|
|
173
|
+
scopeField: fieldName,
|
|
172
174
|
enabled: open && targetDef != null && !blocked
|
|
173
175
|
});
|
|
174
176
|
const rows = listQuery.data?.data ?? [];
|
|
@@ -203,7 +205,6 @@ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, c
|
|
|
203
205
|
open: openMode,
|
|
204
206
|
testId: tid.relationManyChip(resource, fieldName, String(key)),
|
|
205
207
|
disabled: disabled === true,
|
|
206
|
-
ignoreScope: fixedFilters !== void 0 && fixedFilters.length > 0,
|
|
207
208
|
focusTrigger,
|
|
208
209
|
onRemove: () => onChange(keys.filter((entry) => String(entry) !== String(key)))
|
|
209
210
|
}, String(key)))
|
|
@@ -401,7 +402,7 @@ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, c
|
|
|
401
402
|
]
|
|
402
403
|
})
|
|
403
404
|
}),
|
|
404
|
-
dialogMounted && !gated && (0, react_jsx_runtime.jsx)(
|
|
405
|
+
dialogMounted && !gated && (0, react_jsx_runtime.jsx)(require_candidates_gate.LazyRelationPickerDialog, {
|
|
405
406
|
to,
|
|
406
407
|
open: dialogOpen,
|
|
407
408
|
onOpenChange: setDialogOpen,
|
|
@@ -412,6 +413,8 @@ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, c
|
|
|
412
413
|
valueField,
|
|
413
414
|
searchField,
|
|
414
415
|
fixedFilters,
|
|
416
|
+
scope,
|
|
417
|
+
scopeField: fieldName,
|
|
415
418
|
...disabled ? {} : { create },
|
|
416
419
|
createSeeds,
|
|
417
420
|
fieldName,
|
|
@@ -421,7 +424,7 @@ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, c
|
|
|
421
424
|
]
|
|
422
425
|
});
|
|
423
426
|
}
|
|
424
|
-
function Chip({ to, keyValue, by, resource, fieldName, testId, open: openMode, disabled,
|
|
427
|
+
function Chip({ to, keyValue, by, resource, fieldName, testId, open: openMode, disabled, focusTrigger, onRemove }) {
|
|
425
428
|
const t = (0, _burojs_react.useOptionalT)();
|
|
426
429
|
const targetDef = (0, _burojs_react.useApp)().registry.get(to);
|
|
427
430
|
const rowTitle = require_search_access.useRowTitle(to, targetDef?.displayField);
|
|
@@ -440,7 +443,6 @@ function Chip({ to, keyValue, by, resource, fieldName, testId, open: openMode, d
|
|
|
440
443
|
to,
|
|
441
444
|
value: keyValue,
|
|
442
445
|
by,
|
|
443
|
-
ignoreScope,
|
|
444
446
|
fieldName: `${fieldName}:${String(keyValue)}`,
|
|
445
447
|
beforeOpen: focusTrigger,
|
|
446
448
|
open: openMode
|
|
@@ -462,4 +464,4 @@ function Chip({ to, keyValue, by, resource, fieldName, testId, open: openMode, d
|
|
|
462
464
|
//#endregion
|
|
463
465
|
exports.RelationManyPickerInput = RelationManyPickerInput;
|
|
464
466
|
|
|
465
|
-
//# sourceMappingURL=relation-many-picker-
|
|
467
|
+
//# sourceMappingURL=relation-many-picker-C8zFEjwd.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relation-many-picker-C8zFEjwd.cjs","names":["selectKey","resourceKey","useCandidatesGate","Button","relationOpenOf","useApp","useI18nCaps","useOptionalT","useTestIds","useRowTitle","resolveResourceLabel","React","useListAccess","narrowSearchDecl","composeListFilter","normalizeFixedFilters","namesHiddenField","useSelectCreate","useList","IconLoader2","IconChevronDown","Popover","PopoverTrigger","PopoverContent","Command","CommandInput","CommandList","CommandEmpty","CommandItem","IconCheck","cn","RelationCreateButton","LazyRelationPickerDialog","useRelatedRecord","RelationViewButton","IconX"],"sources":["../src/fields/relation-many-picker.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ErasedResourceHandle,\n type FilterExpression,\n type FixedFilterInput,\n type RelationCandidate,\n type RelationOpen,\n relationOpenOf,\n type ResourceRefInput,\n type ScopeSource,\n type SearchDecl,\n composeListFilter,\n normalizeFixedFilters,\n resolveResourceLabel,\n resourceKey,\n} from '@burojs/core';\nimport {\n useApp,\n useI18nCaps,\n useList,\n useListAccess,\n useOptionalT,\n useRelatedRecord,\n useTestIds,\n} from '@burojs/react';\nimport { IconCheck, IconChevronDown, IconLoader2, IconX } from '@tabler/icons-react';\nimport * as React from 'react';\nimport { cn } from '../lib/cn.js';\nimport { Button } from '../primitives/button.js';\nimport {\n Command,\n CommandEmpty,\n CommandInput,\n CommandItem,\n CommandList,\n} from '../primitives/command.js';\nimport { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover.js';\nimport { useRowTitle } from '../resource/use-row-title.js';\nimport { useCandidatesGate } from './candidates-gate.js';\nimport { RelationCreateButton, useSelectCreate } from './relation-create.js';\nimport { LazyRelationPickerDialog } from './lazy-relation-picker-dialog.js';\nimport { RelationViewButton, selectKey } from './relation-view-button.js';\nimport type { FieldInputProps } from './registry.js';\nimport { namesHiddenField, narrowSearchDecl } from './search-access.js';\n\ntype ErasedExpression = FilterExpression<ErasedResourceHandle>;\ntype Key = string | number;\n\nconst DEBOUNCE_MS = 250;\n\n/** The keys a to-many field holds: strings and finite numbers, the user's order, no repeats. */\nfunction keysOf(value: unknown): readonly Key[] {\n if (!Array.isArray(value)) return [];\n const seen = new Set<string>();\n const out: Key[] = [];\n for (const entry of value) {\n const key = selectKey(entry);\n if (key === null || seen.has(key)) continue;\n seen.add(key);\n out.push(entry as Key);\n }\n return out;\n}\n\n/**\n * Form input of `f.relationMany` (P7 3.5): the chosen rows as chips (titles through the relation\n * aggregator, one batched read), a popover that searches the same option query a single select\n * does (candidates, field access and the create hand-back included), \"Advanced search\" in a\n * multiple-choice dialog, a \"view\" on every chip and \"+ create\" that appends what it created.\n * The value is `row[by ?? id]` for each chosen row. Loaded lazily: it is not on any screen's\n * first paint.\n */\nexport function RelationManyPickerInput(props: FieldInputProps): React.ReactElement {\n const {\n name,\n id,\n value,\n onChange,\n testid,\n field,\n disabled,\n required,\n ariaInvalid,\n ariaDescribedBy,\n ariaErrorMessage,\n relationSeeds,\n fixedFilters,\n metaSources,\n resource,\n } = props;\n const meta = field?.meta as\n | { to?: ResourceRefInput; by?: unknown; searchField?: string; candidates?: unknown }\n | undefined;\n let to: string | undefined;\n try {\n to = meta?.to === undefined ? undefined : resourceKey(meta.to);\n } catch {\n to = undefined;\n }\n const gate = useCandidatesGate({\n to,\n candidates: Array.isArray(meta?.candidates)\n ? (meta.candidates as readonly RelationCandidate[])\n : undefined,\n sources: metaSources,\n resource: resource ?? '',\n fieldName: field?.name ?? name,\n });\n if (!to) {\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={`${testid ?? `relation-many:${name}`}:no-target`}\n className=\"w-full justify-between font-normal\"\n name={name}\n id={id}\n >\n <span className=\"truncate text-muted-foreground\">—</span>\n </Button>\n );\n }\n const filters =\n gate.filters.length === 0 ? fixedFilters : [...gate.filters, ...(fixedFilters ?? [])];\n return (\n <ManyPicker\n to={to}\n keys={keysOf(value)}\n onChange={(next) => onChange(next)}\n {...(typeof meta?.by === 'string' ? { by: meta.by } : {})}\n searchField={meta?.searchField}\n fixedFilters={filters}\n {...(gate.gate === undefined ? {} : { gate: gate.gate })}\n createSeeds={relationSeeds}\n name={name}\n id={id}\n testid={testid ?? `relation-many:${to}`}\n resource={resource ?? ''}\n fieldName={field?.name ?? name}\n open={relationOpenOf(field ?? {})}\n disabled={disabled}\n required={required}\n ariaInvalid={ariaInvalid}\n ariaDescribedBy={ariaDescribedBy}\n ariaErrorMessage={ariaErrorMessage}\n />\n );\n}\n\ninterface ManyPickerProps {\n readonly to: string;\n readonly keys: readonly Key[];\n readonly onChange: (next: readonly Key[]) => void;\n readonly by?: string;\n readonly searchField?: string | undefined;\n readonly fixedFilters: readonly FixedFilterInput[] | undefined;\n readonly gate?: import('./candidates-gate.js').CandidatesGate;\n readonly createSeeds: Readonly<Record<string, unknown>> | undefined;\n readonly name: string;\n readonly id: string | undefined;\n readonly testid: string;\n readonly resource: string;\n readonly fieldName: string;\n readonly open: RelationOpen;\n /**\n * The read scope of the option reads (popover, dialog, create hand-back). Default `'context'`:\n * the record around the select narrows them, except by the field the select writes.\n */\n readonly scope?: ScopeSource;\n readonly disabled: boolean | undefined;\n readonly required: boolean | undefined;\n readonly ariaInvalid: boolean | undefined;\n readonly ariaDescribedBy: string | undefined;\n readonly ariaErrorMessage: string | undefined;\n}\n\nfunction ManyPicker({\n to,\n keys,\n onChange,\n by,\n searchField,\n fixedFilters,\n gate,\n createSeeds,\n name,\n id,\n testid,\n resource,\n fieldName,\n open: openMode,\n scope = 'context',\n disabled,\n required,\n ariaInvalid,\n ariaDescribedBy,\n ariaErrorMessage,\n}: ManyPickerProps): React.ReactElement {\n const app = useApp();\n const caps = useI18nCaps();\n const t = useOptionalT();\n const tid = useTestIds();\n const targetDef = app.registry.get(to);\n const rowTitle = useRowTitle(to, targetDef?.displayField);\n const targetLabel = resolveResourceLabel(caps, to, 'single', targetDef).toLowerCase();\n const childPrefix = `relation-picker:${to}`;\n const effectiveSearchField = searchField ?? targetDef?.displayField ?? 'name';\n const [open, setOpen] = React.useState(false);\n const [dialogOpen, setDialogOpen] = React.useState(false);\n // Mounted on the first open, and only while no candidates gate holds the read: without the\n // candidate filters the dialog would list rows the field must not offer.\n const gated = gate !== undefined;\n const [dialogMounted, setDialogMounted] = React.useState(false);\n React.useEffect(() => {\n if (gated) setDialogOpen(false);\n }, [gated]);\n React.useEffect(() => {\n if (dialogOpen) setDialogMounted(true);\n }, [dialogOpen]);\n const [search, setSearch] = React.useState('');\n const [debouncedSearch, setDebouncedSearch] = React.useState('');\n const triggerRef = React.useRef<HTMLButtonElement | null>(null);\n const focusTrigger = React.useCallback(() => triggerRef.current?.focus(), []);\n\n React.useEffect(() => {\n const handle = setTimeout(() => setDebouncedSearch(search), DEBOUNCE_MS);\n return () => clearTimeout(handle);\n }, [search]);\n React.useEffect(() => {\n if (!open) {\n setSearch('');\n setDebouncedSearch('');\n }\n }, [open]);\n\n const targetIdField = (targetDef?.data as { readonly idField?: unknown } | undefined)?.idField;\n const idField =\n typeof targetIdField === 'string' && targetIdField.length > 0 ? targetIdField : 'id';\n const valueField = by ?? idField;\n const access = useListAccess(to);\n const accessReady = access.status === 'ready';\n const searchDecl = React.useMemo<SearchDecl | undefined>(\n () =>\n narrowSearchDecl(\n targetDef?.views?.list?.search ?? { fields: [effectiveSearchField] },\n (field) => access.fieldVisible(field),\n ),\n [targetDef, effectiveSearchField, access],\n );\n const filter = React.useMemo<ErasedExpression | undefined>(\n () =>\n composeListFilter({\n fixed: normalizeFixedFilters(fixedFilters),\n search: debouncedSearch,\n searchDecl,\n searchContext: { resource: to, def: targetDef },\n }),\n [fixedFilters, debouncedSearch, searchDecl, to, targetDef],\n );\n const blocked =\n !accessReady ||\n gate !== undefined ||\n namesHiddenField(filter, (field) => access.fieldVisible(field));\n\n const append = React.useCallback(\n (next: Key) => {\n if (keys.some((key) => String(key) === String(next))) return;\n onChange([...keys, next]);\n },\n [keys, onChange],\n );\n const toggle = React.useCallback(\n (next: Key) => {\n onChange(\n keys.some((key) => String(key) === String(next))\n ? keys.filter((key) => String(key) !== String(next))\n : [...keys, next],\n );\n },\n [keys, onChange],\n );\n const { create, reader: createReader } = useSelectCreate({\n to,\n by,\n valueField,\n idField,\n fixedFilters,\n scope,\n scopeField: fieldName,\n held: gated,\n searchDecl,\n searchContext: { resource: to, def: targetDef },\n // What was created and fits is appended, as a pick would be; the dialog it came from is done.\n onValue: (picked) => {\n append(picked);\n setDialogOpen(false);\n },\n });\n\n const listQuery = useList(to, {\n ...(filter ? { filter } : {}),\n pagination: { kind: 'offset', page: 1, pageSize: 10 },\n scope,\n scopeField: fieldName,\n enabled: open && targetDef != null && !blocked,\n });\n const rows = (listQuery.data?.data ?? []) as Array<Record<string, unknown>>;\n const isLoading = listQuery.isLoading || listQuery.isFetching;\n const error = listQuery.isError ? (listQuery.error as unknown as Error) : null;\n\n if (!targetDef) {\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={`${childPrefix}:unregistered`}\n className=\"w-full justify-between font-normal\"\n name={name}\n id={id}\n >\n <span className=\"truncate text-muted-foreground\">{keys.map(String).join(', ') || '—'}</span>\n </Button>\n );\n }\n\n const placeholder = t('relation.select', {\n params: { name: targetLabel },\n defaultValue: `Select ${targetLabel}…`,\n });\n const chips = (\n <div className=\"flex flex-wrap items-center gap-1\" data-testid={`${testid}:chips`}>\n {keys.map((key) => (\n <Chip\n key={String(key)}\n to={to}\n keyValue={key}\n by={by}\n resource={resource}\n fieldName={fieldName}\n open={openMode}\n testId={tid.relationManyChip(resource, fieldName, String(key))}\n disabled={disabled === true}\n focusTrigger={focusTrigger}\n onRemove={() => onChange(keys.filter((entry) => String(entry) !== String(key)))}\n />\n ))}\n </div>\n );\n\n if (gate?.kind === 'pending')\n return (\n <div className=\"flex flex-col gap-1.5\">\n {chips}\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n aria-busy=\"true\"\n data-testid={testid}\n data-buro-candidates=\"pending\"\n className=\"w-full justify-between font-normal\"\n >\n <span className=\"truncate text-muted-foreground\">{placeholder}</span>\n <IconLoader2 className=\"h-4 w-4 shrink-0 animate-spin opacity-50\" aria-hidden />\n </Button>\n </div>\n );\n if (gate?.kind === 'forbidden')\n return (\n <div className=\"flex flex-col gap-1.5\">\n {chips}\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={testid}\n data-buro-candidates=\"forbidden\"\n aria-describedby={gate.testId}\n className=\"w-full justify-between font-normal\"\n >\n <span className=\"truncate text-muted-foreground\">{placeholder}</span>\n <IconChevronDown className=\"h-4 w-4 shrink-0 opacity-50\" aria-hidden />\n </Button>\n <p id={gate.testId} data-testid={gate.testId} className=\"text-xs text-muted-foreground\">\n {gate.message}\n </p>\n </div>\n );\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <div className=\"flex flex-col gap-1.5\">\n {chips}\n <PopoverTrigger asChild>\n <Button\n ref={triggerRef}\n type=\"button\"\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-required={required}\n aria-invalid={ariaInvalid || undefined}\n aria-describedby={ariaDescribedBy}\n {...(ariaErrorMessage ? { 'aria-errormessage': ariaErrorMessage } : {})}\n disabled={disabled}\n data-testid={testid}\n name={name}\n id={id}\n className=\"w-full justify-between font-normal\"\n >\n <span className=\"truncate text-left text-muted-foreground\">{placeholder}</span>\n <IconChevronDown className=\"h-4 w-4 shrink-0 opacity-50\" aria-hidden />\n </Button>\n </PopoverTrigger>\n </div>\n <PopoverContent className=\"w-[var(--radix-popover-trigger-width)] p-0\" align=\"start\">\n {gate?.kind === 'empty' ? (\n <p\n className=\"px-3 py-6 text-center text-sm text-muted-foreground\"\n data-testid={gate.testId}\n >\n {gate.message}\n </p>\n ) : (\n <Command shouldFilter={false}>\n {searchDecl !== undefined && (\n <CommandInput\n value={search}\n onValueChange={setSearch}\n placeholder={t('toolbar.search', {\n params: { name: targetLabel },\n defaultValue: `Search ${targetLabel}…`,\n })}\n data-testid={`${childPrefix}:search`}\n aria-label={t('toolbar.search', {\n params: { name: targetLabel },\n defaultValue: `Search ${targetLabel}`,\n })}\n onKeyDown={(event) => {\n // Backspace in an empty search takes the last chip off.\n if (event.key === 'Backspace' && search === '' && keys.length > 0) {\n event.preventDefault();\n onChange(keys.slice(0, -1));\n }\n // Space toggles the highlighted option while nothing is typed.\n if (event.key === ' ' && search === '') {\n event.preventDefault();\n document\n .querySelector<HTMLElement>(\n `[data-testid=\"${childPrefix}:list\"] [aria-selected=\"true\"]`,\n )\n ?.click();\n }\n }}\n />\n )}\n <CommandList\n data-testid={`${childPrefix}:list`}\n aria-multiselectable=\"true\"\n aria-label={targetLabel}\n >\n {isLoading && (\n <div\n className=\"flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground\"\n data-testid={`${childPrefix}:loading`}\n >\n <IconLoader2 className=\"h-4 w-4 animate-spin\" aria-hidden />\n <span>{t('table.loading', { defaultValue: 'Loading…' })}</span>\n </div>\n )}\n {!isLoading && error && (\n <div\n className=\"px-3 py-6 text-center text-sm text-muted-foreground\"\n data-testid={`${childPrefix}:error`}\n title={error.message}\n >\n {t('relation.failed', { defaultValue: 'Failed to load options.' })}\n </div>\n )}\n {!isLoading && !error && rows.length === 0 && (\n <CommandEmpty data-testid={`${childPrefix}:empty`}>\n {t('relation.noResults', { defaultValue: 'No results.' })}\n </CommandEmpty>\n )}\n {!isLoading &&\n !error &&\n rows.map((row) => {\n const rowValue = row[valueField];\n if (rowValue === null || rowValue === undefined || rowValue === '') return null;\n const idStr = String(rowValue);\n const label = rowTitle(row);\n const chosen = keys.some((key) => String(key) === idStr);\n return (\n <CommandItem\n key={idStr}\n value={`${label} ${idStr}`}\n data-testid={`${childPrefix}:option:${idStr}`}\n aria-checked={chosen}\n onSelect={() => toggle(rowValue as Key)}\n >\n <IconCheck\n className={cn(\n 'mr-2 h-4 w-4 shrink-0',\n chosen ? 'opacity-100' : 'opacity-0',\n )}\n aria-hidden\n />\n <span className=\"truncate\">{label}</span>\n {/* A label that already is the id would print it twice (REF-40). */}\n {label === idStr ? null : (\n <span className=\"ml-auto shrink-0 pl-2 text-xs text-muted-foreground\">\n #{idStr}\n </span>\n )}\n </CommandItem>\n );\n })}\n </CommandList>\n <div className=\"flex items-center justify-between gap-2 border-t px-3 py-2 text-sm\">\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n setDialogOpen(true);\n }}\n data-testid={`${childPrefix}:advanced`}\n className=\"text-foreground hover:underline focus:outline-hidden focus-visible:underline\"\n >\n {t('relationPicker.advancedSearch', { defaultValue: 'Advanced search' })}\n </button>\n {!disabled && (\n <RelationCreateButton\n to={to}\n fieldName={fieldName}\n create={create}\n seeds={createSeeds}\n search={search}\n beforeOpen={() => {\n setOpen(false);\n focusTrigger();\n }}\n className=\"text-foreground hover:underline focus:outline-hidden focus-visible:underline disabled:opacity-50\"\n />\n )}\n </div>\n </Command>\n )}\n </PopoverContent>\n {dialogMounted && !gated && (\n <LazyRelationPickerDialog\n to={to}\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n onSelect={() => undefined}\n multiple\n selected={keys}\n onToggle={toggle}\n valueField={valueField}\n searchField={searchField}\n fixedFilters={fixedFilters}\n scope={scope}\n scopeField={fieldName}\n {...(disabled ? {} : { create })}\n createSeeds={createSeeds}\n fieldName={fieldName}\n onBeforeCreate={focusTrigger}\n />\n )}\n {createReader}\n </Popover>\n );\n}\n\n/** One chosen row: its title (the aggregator batches every chip's read), \"view\" and remove. */\nfunction Chip({\n to,\n keyValue,\n by,\n resource,\n fieldName,\n testId,\n open: openMode,\n disabled,\n focusTrigger,\n onRemove,\n}: {\n readonly to: string;\n readonly keyValue: Key;\n readonly by: string | undefined;\n readonly resource: string;\n readonly fieldName: string;\n readonly testId: string;\n readonly open: RelationOpen;\n readonly disabled: boolean;\n readonly focusTrigger: () => void;\n readonly onRemove: () => void;\n}): React.ReactElement {\n const t = useOptionalT();\n const app = useApp();\n const targetDef = app.registry.get(to);\n const rowTitle = useRowTitle(to, targetDef?.displayField);\n const { record, isLoading } = useRelatedRecord(\n to,\n keyValue,\n by === undefined ? undefined : { by },\n );\n const title = record ? rowTitle(record as Record<string, unknown>) : String(keyValue);\n return (\n <span\n data-testid={testId}\n data-resource={resource}\n className=\"inline-flex items-center gap-0.5 rounded-md border bg-secondary pl-2 text-sm text-secondary-foreground\"\n >\n <span className=\"max-w-[16rem] truncate\">\n {isLoading ? t('table.loading', { defaultValue: 'Loading…' }) : title}\n </span>\n <RelationViewButton\n to={to}\n value={keyValue}\n by={by}\n fieldName={`${fieldName}:${String(keyValue)}`}\n beforeOpen={focusTrigger}\n open={openMode}\n />\n {!disabled && (\n <button\n type=\"button\"\n onClick={onRemove}\n data-testid={`${testId}:remove`}\n aria-label={`${t('relationPicker.clear', { defaultValue: 'Clear' })} ${title}`}\n className=\"inline-flex size-6 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <IconX className=\"size-3.5\" aria-hidden />\n </button>\n )}\n </span>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { l as cn } from "./use-theme-dnE3Od3I.js";
|
|
2
|
-
import {
|
|
2
|
+
import { d as PopoverTrigger, s as Popover, u as PopoverContent } from "./toaster-C02XbB9r.js";
|
|
3
3
|
import { t as Button } from "./button-MWVeZaqj.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { i as selectKey, n as useSelectCreate, r as RelationViewButton, t as RelationCreateButton } from "./relation-create-
|
|
7
|
-
import { t as useCandidatesGate } from "./candidates-gate-CX2PBu6l.js";
|
|
4
|
+
import { _ as CommandEmpty, b as CommandItem, d as useRowTitle, h as Command, n as narrowSearchDecl, t as namesHiddenField, x as CommandList, y as CommandInput } from "./search-access-CPPmfuvy.js";
|
|
5
|
+
import { n as useCandidatesGate, r as LazyRelationPickerDialog } from "./candidates-gate-Bb70LPAy.js";
|
|
6
|
+
import { i as selectKey, n as useSelectCreate, r as RelationViewButton, t as RelationCreateButton } from "./relation-create-h6F9B4C4.js";
|
|
8
7
|
import { composeListFilter, normalizeFixedFilters, relationOpenOf, resolveResourceLabel, resourceKey } from "@burojs/core";
|
|
9
8
|
import { useApp, useI18nCaps, useList, useListAccess, useOptionalT, useRelatedRecord, useTestIds } from "@burojs/react";
|
|
10
9
|
import { IconCheck, IconChevronDown, IconLoader2, IconX } from "@tabler/icons-react";
|
|
@@ -76,7 +75,7 @@ function RelationManyPickerInput(props) {
|
|
|
76
75
|
ariaErrorMessage
|
|
77
76
|
});
|
|
78
77
|
}
|
|
79
|
-
function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, createSeeds, name, id, testid, resource, fieldName, open: openMode, disabled, required, ariaInvalid, ariaDescribedBy, ariaErrorMessage }) {
|
|
78
|
+
function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, createSeeds, name, id, testid, resource, fieldName, open: openMode, scope = "context", disabled, required, ariaInvalid, ariaDescribedBy, ariaErrorMessage }) {
|
|
80
79
|
const app = useApp();
|
|
81
80
|
const caps = useI18nCaps();
|
|
82
81
|
const t = useOptionalT();
|
|
@@ -149,6 +148,8 @@ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, c
|
|
|
149
148
|
valueField,
|
|
150
149
|
idField,
|
|
151
150
|
fixedFilters,
|
|
151
|
+
scope,
|
|
152
|
+
scopeField: fieldName,
|
|
152
153
|
held: gated,
|
|
153
154
|
searchDecl,
|
|
154
155
|
searchContext: {
|
|
@@ -167,7 +168,8 @@ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, c
|
|
|
167
168
|
page: 1,
|
|
168
169
|
pageSize: 10
|
|
169
170
|
},
|
|
170
|
-
|
|
171
|
+
scope,
|
|
172
|
+
scopeField: fieldName,
|
|
171
173
|
enabled: open && targetDef != null && !blocked
|
|
172
174
|
});
|
|
173
175
|
const rows = listQuery.data?.data ?? [];
|
|
@@ -202,7 +204,6 @@ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, c
|
|
|
202
204
|
open: openMode,
|
|
203
205
|
testId: tid.relationManyChip(resource, fieldName, String(key)),
|
|
204
206
|
disabled: disabled === true,
|
|
205
|
-
ignoreScope: fixedFilters !== void 0 && fixedFilters.length > 0,
|
|
206
207
|
focusTrigger,
|
|
207
208
|
onRemove: () => onChange(keys.filter((entry) => String(entry) !== String(key)))
|
|
208
209
|
}, String(key)))
|
|
@@ -411,6 +412,8 @@ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, c
|
|
|
411
412
|
valueField,
|
|
412
413
|
searchField,
|
|
413
414
|
fixedFilters,
|
|
415
|
+
scope,
|
|
416
|
+
scopeField: fieldName,
|
|
414
417
|
...disabled ? {} : { create },
|
|
415
418
|
createSeeds,
|
|
416
419
|
fieldName,
|
|
@@ -420,7 +423,7 @@ function ManyPicker({ to, keys, onChange, by, searchField, fixedFilters, gate, c
|
|
|
420
423
|
]
|
|
421
424
|
});
|
|
422
425
|
}
|
|
423
|
-
function Chip({ to, keyValue, by, resource, fieldName, testId, open: openMode, disabled,
|
|
426
|
+
function Chip({ to, keyValue, by, resource, fieldName, testId, open: openMode, disabled, focusTrigger, onRemove }) {
|
|
424
427
|
const t = useOptionalT();
|
|
425
428
|
const targetDef = useApp().registry.get(to);
|
|
426
429
|
const rowTitle = useRowTitle(to, targetDef?.displayField);
|
|
@@ -439,7 +442,6 @@ function Chip({ to, keyValue, by, resource, fieldName, testId, open: openMode, d
|
|
|
439
442
|
to,
|
|
440
443
|
value: keyValue,
|
|
441
444
|
by,
|
|
442
|
-
ignoreScope,
|
|
443
445
|
fieldName: `${fieldName}:${String(keyValue)}`,
|
|
444
446
|
beforeOpen: focusTrigger,
|
|
445
447
|
open: openMode
|
|
@@ -461,4 +463,4 @@ function Chip({ to, keyValue, by, resource, fieldName, testId, open: openMode, d
|
|
|
461
463
|
//#endregion
|
|
462
464
|
export { RelationManyPickerInput };
|
|
463
465
|
|
|
464
|
-
//# sourceMappingURL=relation-many-picker-
|
|
466
|
+
//# sourceMappingURL=relation-many-picker-bvkjkv-k.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relation-many-picker-bvkjkv-k.js","names":[],"sources":["../src/fields/relation-many-picker.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ErasedResourceHandle,\n type FilterExpression,\n type FixedFilterInput,\n type RelationCandidate,\n type RelationOpen,\n relationOpenOf,\n type ResourceRefInput,\n type ScopeSource,\n type SearchDecl,\n composeListFilter,\n normalizeFixedFilters,\n resolveResourceLabel,\n resourceKey,\n} from '@burojs/core';\nimport {\n useApp,\n useI18nCaps,\n useList,\n useListAccess,\n useOptionalT,\n useRelatedRecord,\n useTestIds,\n} from '@burojs/react';\nimport { IconCheck, IconChevronDown, IconLoader2, IconX } from '@tabler/icons-react';\nimport * as React from 'react';\nimport { cn } from '../lib/cn.js';\nimport { Button } from '../primitives/button.js';\nimport {\n Command,\n CommandEmpty,\n CommandInput,\n CommandItem,\n CommandList,\n} from '../primitives/command.js';\nimport { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover.js';\nimport { useRowTitle } from '../resource/use-row-title.js';\nimport { useCandidatesGate } from './candidates-gate.js';\nimport { RelationCreateButton, useSelectCreate } from './relation-create.js';\nimport { LazyRelationPickerDialog } from './lazy-relation-picker-dialog.js';\nimport { RelationViewButton, selectKey } from './relation-view-button.js';\nimport type { FieldInputProps } from './registry.js';\nimport { namesHiddenField, narrowSearchDecl } from './search-access.js';\n\ntype ErasedExpression = FilterExpression<ErasedResourceHandle>;\ntype Key = string | number;\n\nconst DEBOUNCE_MS = 250;\n\n/** The keys a to-many field holds: strings and finite numbers, the user's order, no repeats. */\nfunction keysOf(value: unknown): readonly Key[] {\n if (!Array.isArray(value)) return [];\n const seen = new Set<string>();\n const out: Key[] = [];\n for (const entry of value) {\n const key = selectKey(entry);\n if (key === null || seen.has(key)) continue;\n seen.add(key);\n out.push(entry as Key);\n }\n return out;\n}\n\n/**\n * Form input of `f.relationMany` (P7 3.5): the chosen rows as chips (titles through the relation\n * aggregator, one batched read), a popover that searches the same option query a single select\n * does (candidates, field access and the create hand-back included), \"Advanced search\" in a\n * multiple-choice dialog, a \"view\" on every chip and \"+ create\" that appends what it created.\n * The value is `row[by ?? id]` for each chosen row. Loaded lazily: it is not on any screen's\n * first paint.\n */\nexport function RelationManyPickerInput(props: FieldInputProps): React.ReactElement {\n const {\n name,\n id,\n value,\n onChange,\n testid,\n field,\n disabled,\n required,\n ariaInvalid,\n ariaDescribedBy,\n ariaErrorMessage,\n relationSeeds,\n fixedFilters,\n metaSources,\n resource,\n } = props;\n const meta = field?.meta as\n | { to?: ResourceRefInput; by?: unknown; searchField?: string; candidates?: unknown }\n | undefined;\n let to: string | undefined;\n try {\n to = meta?.to === undefined ? undefined : resourceKey(meta.to);\n } catch {\n to = undefined;\n }\n const gate = useCandidatesGate({\n to,\n candidates: Array.isArray(meta?.candidates)\n ? (meta.candidates as readonly RelationCandidate[])\n : undefined,\n sources: metaSources,\n resource: resource ?? '',\n fieldName: field?.name ?? name,\n });\n if (!to) {\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={`${testid ?? `relation-many:${name}`}:no-target`}\n className=\"w-full justify-between font-normal\"\n name={name}\n id={id}\n >\n <span className=\"truncate text-muted-foreground\">—</span>\n </Button>\n );\n }\n const filters =\n gate.filters.length === 0 ? fixedFilters : [...gate.filters, ...(fixedFilters ?? [])];\n return (\n <ManyPicker\n to={to}\n keys={keysOf(value)}\n onChange={(next) => onChange(next)}\n {...(typeof meta?.by === 'string' ? { by: meta.by } : {})}\n searchField={meta?.searchField}\n fixedFilters={filters}\n {...(gate.gate === undefined ? {} : { gate: gate.gate })}\n createSeeds={relationSeeds}\n name={name}\n id={id}\n testid={testid ?? `relation-many:${to}`}\n resource={resource ?? ''}\n fieldName={field?.name ?? name}\n open={relationOpenOf(field ?? {})}\n disabled={disabled}\n required={required}\n ariaInvalid={ariaInvalid}\n ariaDescribedBy={ariaDescribedBy}\n ariaErrorMessage={ariaErrorMessage}\n />\n );\n}\n\ninterface ManyPickerProps {\n readonly to: string;\n readonly keys: readonly Key[];\n readonly onChange: (next: readonly Key[]) => void;\n readonly by?: string;\n readonly searchField?: string | undefined;\n readonly fixedFilters: readonly FixedFilterInput[] | undefined;\n readonly gate?: import('./candidates-gate.js').CandidatesGate;\n readonly createSeeds: Readonly<Record<string, unknown>> | undefined;\n readonly name: string;\n readonly id: string | undefined;\n readonly testid: string;\n readonly resource: string;\n readonly fieldName: string;\n readonly open: RelationOpen;\n /**\n * The read scope of the option reads (popover, dialog, create hand-back). Default `'context'`:\n * the record around the select narrows them, except by the field the select writes.\n */\n readonly scope?: ScopeSource;\n readonly disabled: boolean | undefined;\n readonly required: boolean | undefined;\n readonly ariaInvalid: boolean | undefined;\n readonly ariaDescribedBy: string | undefined;\n readonly ariaErrorMessage: string | undefined;\n}\n\nfunction ManyPicker({\n to,\n keys,\n onChange,\n by,\n searchField,\n fixedFilters,\n gate,\n createSeeds,\n name,\n id,\n testid,\n resource,\n fieldName,\n open: openMode,\n scope = 'context',\n disabled,\n required,\n ariaInvalid,\n ariaDescribedBy,\n ariaErrorMessage,\n}: ManyPickerProps): React.ReactElement {\n const app = useApp();\n const caps = useI18nCaps();\n const t = useOptionalT();\n const tid = useTestIds();\n const targetDef = app.registry.get(to);\n const rowTitle = useRowTitle(to, targetDef?.displayField);\n const targetLabel = resolveResourceLabel(caps, to, 'single', targetDef).toLowerCase();\n const childPrefix = `relation-picker:${to}`;\n const effectiveSearchField = searchField ?? targetDef?.displayField ?? 'name';\n const [open, setOpen] = React.useState(false);\n const [dialogOpen, setDialogOpen] = React.useState(false);\n // Mounted on the first open, and only while no candidates gate holds the read: without the\n // candidate filters the dialog would list rows the field must not offer.\n const gated = gate !== undefined;\n const [dialogMounted, setDialogMounted] = React.useState(false);\n React.useEffect(() => {\n if (gated) setDialogOpen(false);\n }, [gated]);\n React.useEffect(() => {\n if (dialogOpen) setDialogMounted(true);\n }, [dialogOpen]);\n const [search, setSearch] = React.useState('');\n const [debouncedSearch, setDebouncedSearch] = React.useState('');\n const triggerRef = React.useRef<HTMLButtonElement | null>(null);\n const focusTrigger = React.useCallback(() => triggerRef.current?.focus(), []);\n\n React.useEffect(() => {\n const handle = setTimeout(() => setDebouncedSearch(search), DEBOUNCE_MS);\n return () => clearTimeout(handle);\n }, [search]);\n React.useEffect(() => {\n if (!open) {\n setSearch('');\n setDebouncedSearch('');\n }\n }, [open]);\n\n const targetIdField = (targetDef?.data as { readonly idField?: unknown } | undefined)?.idField;\n const idField =\n typeof targetIdField === 'string' && targetIdField.length > 0 ? targetIdField : 'id';\n const valueField = by ?? idField;\n const access = useListAccess(to);\n const accessReady = access.status === 'ready';\n const searchDecl = React.useMemo<SearchDecl | undefined>(\n () =>\n narrowSearchDecl(\n targetDef?.views?.list?.search ?? { fields: [effectiveSearchField] },\n (field) => access.fieldVisible(field),\n ),\n [targetDef, effectiveSearchField, access],\n );\n const filter = React.useMemo<ErasedExpression | undefined>(\n () =>\n composeListFilter({\n fixed: normalizeFixedFilters(fixedFilters),\n search: debouncedSearch,\n searchDecl,\n searchContext: { resource: to, def: targetDef },\n }),\n [fixedFilters, debouncedSearch, searchDecl, to, targetDef],\n );\n const blocked =\n !accessReady ||\n gate !== undefined ||\n namesHiddenField(filter, (field) => access.fieldVisible(field));\n\n const append = React.useCallback(\n (next: Key) => {\n if (keys.some((key) => String(key) === String(next))) return;\n onChange([...keys, next]);\n },\n [keys, onChange],\n );\n const toggle = React.useCallback(\n (next: Key) => {\n onChange(\n keys.some((key) => String(key) === String(next))\n ? keys.filter((key) => String(key) !== String(next))\n : [...keys, next],\n );\n },\n [keys, onChange],\n );\n const { create, reader: createReader } = useSelectCreate({\n to,\n by,\n valueField,\n idField,\n fixedFilters,\n scope,\n scopeField: fieldName,\n held: gated,\n searchDecl,\n searchContext: { resource: to, def: targetDef },\n // What was created and fits is appended, as a pick would be; the dialog it came from is done.\n onValue: (picked) => {\n append(picked);\n setDialogOpen(false);\n },\n });\n\n const listQuery = useList(to, {\n ...(filter ? { filter } : {}),\n pagination: { kind: 'offset', page: 1, pageSize: 10 },\n scope,\n scopeField: fieldName,\n enabled: open && targetDef != null && !blocked,\n });\n const rows = (listQuery.data?.data ?? []) as Array<Record<string, unknown>>;\n const isLoading = listQuery.isLoading || listQuery.isFetching;\n const error = listQuery.isError ? (listQuery.error as unknown as Error) : null;\n\n if (!targetDef) {\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={`${childPrefix}:unregistered`}\n className=\"w-full justify-between font-normal\"\n name={name}\n id={id}\n >\n <span className=\"truncate text-muted-foreground\">{keys.map(String).join(', ') || '—'}</span>\n </Button>\n );\n }\n\n const placeholder = t('relation.select', {\n params: { name: targetLabel },\n defaultValue: `Select ${targetLabel}…`,\n });\n const chips = (\n <div className=\"flex flex-wrap items-center gap-1\" data-testid={`${testid}:chips`}>\n {keys.map((key) => (\n <Chip\n key={String(key)}\n to={to}\n keyValue={key}\n by={by}\n resource={resource}\n fieldName={fieldName}\n open={openMode}\n testId={tid.relationManyChip(resource, fieldName, String(key))}\n disabled={disabled === true}\n focusTrigger={focusTrigger}\n onRemove={() => onChange(keys.filter((entry) => String(entry) !== String(key)))}\n />\n ))}\n </div>\n );\n\n if (gate?.kind === 'pending')\n return (\n <div className=\"flex flex-col gap-1.5\">\n {chips}\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n aria-busy=\"true\"\n data-testid={testid}\n data-buro-candidates=\"pending\"\n className=\"w-full justify-between font-normal\"\n >\n <span className=\"truncate text-muted-foreground\">{placeholder}</span>\n <IconLoader2 className=\"h-4 w-4 shrink-0 animate-spin opacity-50\" aria-hidden />\n </Button>\n </div>\n );\n if (gate?.kind === 'forbidden')\n return (\n <div className=\"flex flex-col gap-1.5\">\n {chips}\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled\n data-testid={testid}\n data-buro-candidates=\"forbidden\"\n aria-describedby={gate.testId}\n className=\"w-full justify-between font-normal\"\n >\n <span className=\"truncate text-muted-foreground\">{placeholder}</span>\n <IconChevronDown className=\"h-4 w-4 shrink-0 opacity-50\" aria-hidden />\n </Button>\n <p id={gate.testId} data-testid={gate.testId} className=\"text-xs text-muted-foreground\">\n {gate.message}\n </p>\n </div>\n );\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <div className=\"flex flex-col gap-1.5\">\n {chips}\n <PopoverTrigger asChild>\n <Button\n ref={triggerRef}\n type=\"button\"\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-required={required}\n aria-invalid={ariaInvalid || undefined}\n aria-describedby={ariaDescribedBy}\n {...(ariaErrorMessage ? { 'aria-errormessage': ariaErrorMessage } : {})}\n disabled={disabled}\n data-testid={testid}\n name={name}\n id={id}\n className=\"w-full justify-between font-normal\"\n >\n <span className=\"truncate text-left text-muted-foreground\">{placeholder}</span>\n <IconChevronDown className=\"h-4 w-4 shrink-0 opacity-50\" aria-hidden />\n </Button>\n </PopoverTrigger>\n </div>\n <PopoverContent className=\"w-[var(--radix-popover-trigger-width)] p-0\" align=\"start\">\n {gate?.kind === 'empty' ? (\n <p\n className=\"px-3 py-6 text-center text-sm text-muted-foreground\"\n data-testid={gate.testId}\n >\n {gate.message}\n </p>\n ) : (\n <Command shouldFilter={false}>\n {searchDecl !== undefined && (\n <CommandInput\n value={search}\n onValueChange={setSearch}\n placeholder={t('toolbar.search', {\n params: { name: targetLabel },\n defaultValue: `Search ${targetLabel}…`,\n })}\n data-testid={`${childPrefix}:search`}\n aria-label={t('toolbar.search', {\n params: { name: targetLabel },\n defaultValue: `Search ${targetLabel}`,\n })}\n onKeyDown={(event) => {\n // Backspace in an empty search takes the last chip off.\n if (event.key === 'Backspace' && search === '' && keys.length > 0) {\n event.preventDefault();\n onChange(keys.slice(0, -1));\n }\n // Space toggles the highlighted option while nothing is typed.\n if (event.key === ' ' && search === '') {\n event.preventDefault();\n document\n .querySelector<HTMLElement>(\n `[data-testid=\"${childPrefix}:list\"] [aria-selected=\"true\"]`,\n )\n ?.click();\n }\n }}\n />\n )}\n <CommandList\n data-testid={`${childPrefix}:list`}\n aria-multiselectable=\"true\"\n aria-label={targetLabel}\n >\n {isLoading && (\n <div\n className=\"flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground\"\n data-testid={`${childPrefix}:loading`}\n >\n <IconLoader2 className=\"h-4 w-4 animate-spin\" aria-hidden />\n <span>{t('table.loading', { defaultValue: 'Loading…' })}</span>\n </div>\n )}\n {!isLoading && error && (\n <div\n className=\"px-3 py-6 text-center text-sm text-muted-foreground\"\n data-testid={`${childPrefix}:error`}\n title={error.message}\n >\n {t('relation.failed', { defaultValue: 'Failed to load options.' })}\n </div>\n )}\n {!isLoading && !error && rows.length === 0 && (\n <CommandEmpty data-testid={`${childPrefix}:empty`}>\n {t('relation.noResults', { defaultValue: 'No results.' })}\n </CommandEmpty>\n )}\n {!isLoading &&\n !error &&\n rows.map((row) => {\n const rowValue = row[valueField];\n if (rowValue === null || rowValue === undefined || rowValue === '') return null;\n const idStr = String(rowValue);\n const label = rowTitle(row);\n const chosen = keys.some((key) => String(key) === idStr);\n return (\n <CommandItem\n key={idStr}\n value={`${label} ${idStr}`}\n data-testid={`${childPrefix}:option:${idStr}`}\n aria-checked={chosen}\n onSelect={() => toggle(rowValue as Key)}\n >\n <IconCheck\n className={cn(\n 'mr-2 h-4 w-4 shrink-0',\n chosen ? 'opacity-100' : 'opacity-0',\n )}\n aria-hidden\n />\n <span className=\"truncate\">{label}</span>\n {/* A label that already is the id would print it twice (REF-40). */}\n {label === idStr ? null : (\n <span className=\"ml-auto shrink-0 pl-2 text-xs text-muted-foreground\">\n #{idStr}\n </span>\n )}\n </CommandItem>\n );\n })}\n </CommandList>\n <div className=\"flex items-center justify-between gap-2 border-t px-3 py-2 text-sm\">\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n setDialogOpen(true);\n }}\n data-testid={`${childPrefix}:advanced`}\n className=\"text-foreground hover:underline focus:outline-hidden focus-visible:underline\"\n >\n {t('relationPicker.advancedSearch', { defaultValue: 'Advanced search' })}\n </button>\n {!disabled && (\n <RelationCreateButton\n to={to}\n fieldName={fieldName}\n create={create}\n seeds={createSeeds}\n search={search}\n beforeOpen={() => {\n setOpen(false);\n focusTrigger();\n }}\n className=\"text-foreground hover:underline focus:outline-hidden focus-visible:underline disabled:opacity-50\"\n />\n )}\n </div>\n </Command>\n )}\n </PopoverContent>\n {dialogMounted && !gated && (\n <LazyRelationPickerDialog\n to={to}\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n onSelect={() => undefined}\n multiple\n selected={keys}\n onToggle={toggle}\n valueField={valueField}\n searchField={searchField}\n fixedFilters={fixedFilters}\n scope={scope}\n scopeField={fieldName}\n {...(disabled ? {} : { create })}\n createSeeds={createSeeds}\n fieldName={fieldName}\n onBeforeCreate={focusTrigger}\n />\n )}\n {createReader}\n </Popover>\n );\n}\n\n/** One chosen row: its title (the aggregator batches every chip's read), \"view\" and remove. */\nfunction Chip({\n to,\n keyValue,\n by,\n resource,\n fieldName,\n testId,\n open: openMode,\n disabled,\n focusTrigger,\n onRemove,\n}: {\n readonly to: string;\n readonly keyValue: Key;\n readonly by: string | undefined;\n readonly resource: string;\n readonly fieldName: string;\n readonly testId: string;\n readonly open: RelationOpen;\n readonly disabled: boolean;\n readonly focusTrigger: () => void;\n readonly onRemove: () => void;\n}): React.ReactElement {\n const t = useOptionalT();\n const app = useApp();\n const targetDef = app.registry.get(to);\n const rowTitle = useRowTitle(to, targetDef?.displayField);\n const { record, isLoading } = useRelatedRecord(\n to,\n keyValue,\n by === undefined ? undefined : { by },\n );\n const title = record ? rowTitle(record as Record<string, unknown>) : String(keyValue);\n return (\n <span\n data-testid={testId}\n data-resource={resource}\n className=\"inline-flex items-center gap-0.5 rounded-md border bg-secondary pl-2 text-sm text-secondary-foreground\"\n >\n <span className=\"max-w-[16rem] truncate\">\n {isLoading ? t('table.loading', { defaultValue: 'Loading…' }) : title}\n </span>\n <RelationViewButton\n to={to}\n value={keyValue}\n by={by}\n fieldName={`${fieldName}:${String(keyValue)}`}\n beforeOpen={focusTrigger}\n open={openMode}\n />\n {!disabled && (\n <button\n type=\"button\"\n onClick={onRemove}\n data-testid={`${testId}:remove`}\n aria-label={`${t('relationPicker.clear', { defaultValue: 'Clear' })} ${title}`}\n className=\"inline-flex size-6 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <IconX className=\"size-3.5\" aria-hidden />\n </button>\n )}\n </span>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { l as cn } from "./use-theme-dnE3Od3I.js";
|
|
2
|
-
import { d as
|
|
2
|
+
import { d as PopoverTrigger, o as useLinkComponent, s as Popover, u as PopoverContent } from "./toaster-C02XbB9r.js";
|
|
3
3
|
import { t as Button } from "./button-MWVeZaqj.js";
|
|
4
|
-
import {
|
|
4
|
+
import { _ as CommandEmpty, b as CommandItem, d as useRowTitle, h as Command, n as narrowSearchDecl, t as namesHiddenField, x as CommandList, y as CommandInput } from "./search-access-CPPmfuvy.js";
|
|
5
5
|
import { t as Skeleton } from "./skeleton-U2noefj5.js";
|
|
6
|
-
import {
|
|
7
|
-
import { n as useSelectCreate, r as RelationViewButton, t as RelationCreateButton } from "./relation-create-
|
|
6
|
+
import { r as LazyRelationPickerDialog } from "./candidates-gate-Bb70LPAy.js";
|
|
7
|
+
import { n as useSelectCreate, r as RelationViewButton, t as RelationCreateButton } from "./relation-create-h6F9B4C4.js";
|
|
8
8
|
import { composeListFilter, normalizeFixedFilters, relationOpenOf, resolveResourceLabel, resourceKey } from "@burojs/core";
|
|
9
9
|
import { useApp, useI18nCaps, useList, useListAccess, useOptionalT, useRelatedRecord } from "@burojs/react";
|
|
10
10
|
import { IconCheck, IconChevronDown, IconLoader2 } from "@tabler/icons-react";
|
|
@@ -12,7 +12,7 @@ import * as React from "react";
|
|
|
12
12
|
import { Suspense, lazy } from "react";
|
|
13
13
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
14
|
//#region src/fields/lazy-candidates-picker.tsx
|
|
15
|
-
const CandidatesRelationPicker = lazy(() => import("./candidates-relation-picker-
|
|
15
|
+
const CandidatesRelationPicker = lazy(() => import("./candidates-relation-picker-B116Bmqi.js").then((module) => ({ default: module.CandidatesRelationPicker })));
|
|
16
16
|
function LazyCandidatesRelationPicker(props) {
|
|
17
17
|
return jsx(Suspense, {
|
|
18
18
|
fallback: jsx(Skeleton, {
|
|
@@ -24,9 +24,51 @@ function LazyCandidatesRelationPicker(props) {
|
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
//#endregion
|
|
27
|
+
//#region src/fields/relation-select.ts
|
|
28
|
+
function useRelationSelect(input) {
|
|
29
|
+
const { to, by, searchField, fixedFilters, scope, scopeField, held, onValue } = input;
|
|
30
|
+
const app = useApp();
|
|
31
|
+
const targetDef = to === "" ? void 0 : app.registry.get(to);
|
|
32
|
+
const targetIdField = (targetDef?.data)?.idField;
|
|
33
|
+
const idField = typeof targetIdField === "string" && targetIdField.length > 0 ? targetIdField : "id";
|
|
34
|
+
const valueField = by ?? idField;
|
|
35
|
+
const effectiveSearchField = searchField ?? targetDef?.displayField ?? "name";
|
|
36
|
+
const access = useListAccess(to);
|
|
37
|
+
const searchDecl = React.useMemo(() => narrowSearchDecl(targetDef?.views?.list?.search ?? { fields: [effectiveSearchField] }, (name) => access.fieldVisible(name)), [
|
|
38
|
+
targetDef,
|
|
39
|
+
effectiveSearchField,
|
|
40
|
+
access
|
|
41
|
+
]);
|
|
42
|
+
const { create, reader } = useSelectCreate({
|
|
43
|
+
to,
|
|
44
|
+
by,
|
|
45
|
+
valueField,
|
|
46
|
+
idField,
|
|
47
|
+
fixedFilters,
|
|
48
|
+
scope,
|
|
49
|
+
...scopeField === void 0 ? {} : { scopeField },
|
|
50
|
+
held,
|
|
51
|
+
searchDecl,
|
|
52
|
+
searchContext: {
|
|
53
|
+
resource: to,
|
|
54
|
+
def: targetDef
|
|
55
|
+
},
|
|
56
|
+
onValue
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
targetDef,
|
|
60
|
+
access,
|
|
61
|
+
idField,
|
|
62
|
+
valueField,
|
|
63
|
+
searchDecl,
|
|
64
|
+
create,
|
|
65
|
+
reader
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
27
69
|
//#region src/fields/relation-picker.tsx
|
|
28
70
|
const DEBOUNCE_MS = 250;
|
|
29
|
-
function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, clearable = true, placeholder, name, id, testid, fixedFilters, createSeeds, candidatesGate, rowFits, fieldName, open: openMode, disabled, required, className, ariaInvalid, ariaDescribedBy, ariaErrorMessage }) {
|
|
71
|
+
function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, clearable = true, placeholder, name, id, testid, fixedFilters, createSeeds, candidatesGate, rowFits, fieldName, open: openMode, scope = "context", disabled, required, className, ariaInvalid, ariaDescribedBy, ariaErrorMessage }) {
|
|
30
72
|
const app = useApp();
|
|
31
73
|
const caps = useI18nCaps();
|
|
32
74
|
const t = useOptionalT();
|
|
@@ -36,7 +78,6 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
|
|
|
36
78
|
const RouterLink = useLinkComponent();
|
|
37
79
|
const tid = testid ?? `relation-picker:${to}`;
|
|
38
80
|
const childPrefix = `relation-picker:${to}`;
|
|
39
|
-
const effectiveSearchField = searchField ?? targetDef?.displayField ?? "name";
|
|
40
81
|
const triggerRef = React.useRef(null);
|
|
41
82
|
const focusTrigger = React.useCallback(() => triggerRef.current?.focus(), []);
|
|
42
83
|
const [open, setOpen] = React.useState(false);
|
|
@@ -62,15 +103,21 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
|
|
|
62
103
|
}
|
|
63
104
|
}, [open]);
|
|
64
105
|
const { record: selectedRecord, isLoading: selectedLoading } = useRelatedRecord(to, value, by === void 0 ? void 0 : { by });
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
106
|
+
const scopeField = fieldName ?? name;
|
|
107
|
+
const { access, valueField, searchDecl, create, reader: createReader } = useRelationSelect({
|
|
108
|
+
to,
|
|
109
|
+
by,
|
|
110
|
+
searchField,
|
|
111
|
+
fixedFilters,
|
|
112
|
+
scope,
|
|
113
|
+
scopeField,
|
|
114
|
+
held: gated,
|
|
115
|
+
onValue: (picked) => {
|
|
116
|
+
onChange(picked);
|
|
117
|
+
setDialogOpen(false);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
68
120
|
const accessReady = access.status === "ready";
|
|
69
|
-
const searchDecl = React.useMemo(() => narrowSearchDecl(targetDef?.views?.list?.search ?? { fields: [effectiveSearchField] }, (name) => access.fieldVisible(name)), [
|
|
70
|
-
targetDef,
|
|
71
|
-
effectiveSearchField,
|
|
72
|
-
access
|
|
73
|
-
]);
|
|
74
121
|
const filter = React.useMemo(() => composeListFilter({
|
|
75
122
|
fixed: normalizeFixedFilters(fixedFilters),
|
|
76
123
|
search: debouncedSearch,
|
|
@@ -87,24 +134,6 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
|
|
|
87
134
|
targetDef
|
|
88
135
|
]);
|
|
89
136
|
const blocked = !accessReady || candidatesGate !== void 0 || namesHiddenField(filter, (name) => access.fieldVisible(name));
|
|
90
|
-
const idField = typeof targetIdField === "string" && targetIdField.length > 0 ? targetIdField : "id";
|
|
91
|
-
const { create, reader: createReader } = useSelectCreate({
|
|
92
|
-
to,
|
|
93
|
-
by,
|
|
94
|
-
valueField,
|
|
95
|
-
idField,
|
|
96
|
-
fixedFilters,
|
|
97
|
-
held: gated,
|
|
98
|
-
searchDecl,
|
|
99
|
-
searchContext: {
|
|
100
|
-
resource: to,
|
|
101
|
-
def: targetDef
|
|
102
|
-
},
|
|
103
|
-
onValue: (picked) => {
|
|
104
|
-
onChange(picked);
|
|
105
|
-
setDialogOpen(false);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
137
|
const listQuery = useList(to, {
|
|
109
138
|
...filter ? { filter } : {},
|
|
110
139
|
pagination: {
|
|
@@ -112,7 +141,8 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
|
|
|
112
141
|
page: 1,
|
|
113
142
|
pageSize
|
|
114
143
|
},
|
|
115
|
-
|
|
144
|
+
scope,
|
|
145
|
+
...scopeField === void 0 ? {} : { scopeField },
|
|
116
146
|
enabled: open && targetDef != null && !blocked
|
|
117
147
|
});
|
|
118
148
|
const rows = listQuery.data?.data ?? [];
|
|
@@ -242,7 +272,6 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
|
|
|
242
272
|
to,
|
|
243
273
|
value,
|
|
244
274
|
by,
|
|
245
|
-
ignoreScope: fixedFilters !== void 0 && fixedFilters.length > 0,
|
|
246
275
|
fieldName: fieldName ?? name ?? to,
|
|
247
276
|
beforeOpen: focusTrigger,
|
|
248
277
|
...openMode === void 0 ? {} : { open: openMode }
|
|
@@ -378,6 +407,8 @@ function RelationPicker({ to, value, onChange, by, searchField, pageSize = 10, c
|
|
|
378
407
|
valueField,
|
|
379
408
|
searchField,
|
|
380
409
|
fixedFilters,
|
|
410
|
+
scope,
|
|
411
|
+
...scopeField === void 0 ? {} : { scopeField },
|
|
381
412
|
...disabled ? {} : { create },
|
|
382
413
|
createSeeds,
|
|
383
414
|
fieldName: fieldName ?? name ?? to,
|
|
@@ -457,6 +488,6 @@ function FieldRelationPicker({ props, to, extra }) {
|
|
|
457
488
|
});
|
|
458
489
|
}
|
|
459
490
|
//#endregion
|
|
460
|
-
export { RelationPicker as n, RelationPickerInput as r, FieldRelationPicker as t };
|
|
491
|
+
export { useRelationSelect as i, RelationPicker as n, RelationPickerInput as r, FieldRelationPicker as t };
|
|
461
492
|
|
|
462
|
-
//# sourceMappingURL=relation-picker-
|
|
493
|
+
//# sourceMappingURL=relation-picker-Z0ZJJ5uS.js.map
|