fix(ui): preserve polymorphic upload bulk select relation (#17112)

Port of https://github.com/payloadcms/payload/pull/16695 to `3.x`
This commit is contained in:
Sasha
2026-06-25 16:16:52 +00:00
committed by GitHub
parent ac000e535a
commit 9691e42bf8
6 changed files with 87 additions and 5 deletions
@@ -197,7 +197,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
DocumentDrawerToggler={DocumentDrawerToggler}
drawerSlug={drawerSlug}
enabledCollections={collectionSlugs}
onBulkSelect={onBulkSelect}
onBulkSelect={(selected) => onBulkSelect?.(selected, selectedOption.value)}
onQueryChange={onQueryChange}
onSelect={onSelect}
refresh={refreshSelf}
@@ -12,7 +12,10 @@ export type ListDrawerContextProps = {
readonly DocumentDrawerToggler?: ReturnType<UseDocumentDrawer>[1]
readonly drawerSlug?: string
readonly enabledCollections?: CollectionSlug[]
readonly onBulkSelect?: (selected: ReturnType<typeof useSelection>['selected']) => void
readonly onBulkSelect?: (
selected: ReturnType<typeof useSelection>['selected'],
collectionSlug?: CollectionSlug,
) => void
readonly onQueryChange?: (query: ListQuery) => void
readonly onSelect?: (args: {
collectionSlug: CollectionSlug
+4 -3
View File
@@ -413,8 +413,9 @@ export function UploadInput(props: UploadInputProps) {
// only hasMany can bulk select
const onListBulkSelect = React.useCallback<NonNullable<ListDrawerProps['onBulkSelect']>>(
async (docs) => {
async (docs, collectionSlug) => {
const isPoly = Array.isArray(relationTo)
const relationToUse = isPoly ? collectionSlug || activeRelationTo : activeRelationTo
const selectedDocIDs = []
for (const [id, isSelected] of docs) {
@@ -424,7 +425,7 @@ export function UploadInput(props: UploadInputProps) {
}
const itemsToLoad = selectedDocIDs.map((id) => ({
relationTo: activeRelationTo,
relationTo: relationToUse,
value: id,
}))
@@ -434,7 +435,7 @@ export function UploadInput(props: UploadInputProps) {
}
const newValues = selectedDocIDs.map((id) =>
isPoly ? { relationTo: activeRelationTo, value: id } : id,
isPoly ? { relationTo: relationToUse, value: id } : id,
)
// Normalize existing values before merging
const normalizedExisting = Array.isArray(value) ? value.map(normalizeValue) : []
+6
View File
@@ -86,6 +86,12 @@ export default buildConfigWithDefaults({
type: 'upload',
relationTo: hideFileInputOnCreateSlug,
},
{
name: 'polymorphicUploads',
type: 'upload',
relationTo: ['uploads-1', 'uploads-2'],
hasMany: true,
},
{
type: 'tabs',
tabs: [
+59
View File
@@ -276,6 +276,65 @@ describe('Uploads', () => {
await expect(filename).toContainText('test-image.png')
})
test('should preserve collection when bulk selecting polymorphic uploads', async () => {
await page.goto(uploadsTwo.create)
await page.locator('#field-prefix').fill('video')
await page.locator('#field-title').fill('Polymorphic upload two')
await page.setInputFiles('input[type="file"]', path.resolve(dirname, './image.png'))
await saveDocAndAssert(page)
const uploadTwoID = page.url().split('/').pop()
const relationDoc = await payload.create({
collection: relationSlug,
data: {},
})
await page.goto(relationURL.edit(relationDoc.id))
await openDocDrawer({ page, selector: '#field-polymorphicUploads .upload__listToggler' })
const listDrawer = page.locator('[id^=list-drawer_1_]')
await expect(listDrawer).toBeVisible()
await listDrawer.locator('.list-header__select-collection').click()
await page.getByText('Uploads 2', { exact: true }).click()
await expect(
listDrawer.locator('.cell-title', { hasText: 'Polymorphic upload two' }),
).toBeVisible()
await listDrawer
.locator('tr', { hasText: 'Polymorphic upload two' })
.locator('.select-row__checkbox')
.click()
await listDrawer.getByRole('button', { name: 'Select 1' }).click()
await saveDocAndAssert(page)
const updatedRelationDoc = (
await payload.find({
collection: relationSlug,
depth: 0,
where: {
id: {
equals: relationDoc.id,
},
},
})
).docs[0] as any
expect(updatedRelationDoc.polymorphicUploads).toEqual([
{
relationTo: 'uploads-2',
value: uploadTwoID,
},
])
expect(updatedRelationDoc.polymorphicUploads).not.toEqual([
{
relationTo: 'uploads-1',
value: uploadTwoID,
},
])
})
test('should copy the file url field to the clipboard', async () => {
const mediaDoc = (
await payload.find({
+13
View File
@@ -246,6 +246,18 @@ export interface Relation {
image?: (string | null) | Media;
versionedImage?: (string | null) | Version;
hideFileInputOnCreate?: (string | null) | HideFileInputOnCreate;
polymorphicUploads?:
| (
| {
relationTo: 'uploads-1';
value: string | Uploads1;
}
| {
relationTo: 'uploads-2';
value: string | Uploads2;
}
)[]
| null;
blocks?:
| {
media: string | Media;
@@ -2197,6 +2209,7 @@ export interface RelationSelect<T extends boolean = true> {
image?: T;
versionedImage?: T;
hideFileInputOnCreate?: T;
polymorphicUploads?: T;
blocks?:
| T
| {