fix(next): force inactive live preview after passing conditions (#14048)

Follow-up to #14012.

Once live preview conditions have passed, it is jarring for the live
preview window to suddenly appear. It should be that, despite
preferences, if the live preview window did not _load_ active, then it
should not become active until the user explicitly toggles it on.

This is especially poor UX while creating a new doc. If the conditional
URL is based on a field that has't been filled yet, upon filling that
field (with autosave), live preview suddenly appears and the entire page
shifts mid-edit.

Before:


https://github.com/user-attachments/assets/0da75306-eed3-4a77-bc58-d8a8dd0254bf

After:


https://github.com/user-attachments/assets/c7918601-959d-4ac5-b168-066afc3d879d

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211534009142634
This commit is contained in:
Jacob Fletcher
2025-10-03 12:09:02 -04:00
committed by GitHub
parent cb7a24ad70
commit ca3f054041
6 changed files with 43 additions and 12 deletions

View File

@@ -1,7 +1,7 @@
import type { CollectionConfig } from 'payload'
export const NoURLCollection: CollectionConfig = {
slug: 'no-url',
export const ConditionalURL: CollectionConfig = {
slug: 'conditional-url',
admin: {
livePreview: {
url: ({ data }) => (data?.enabled ? '/live-preview/static' : null),

View File

@@ -4,7 +4,7 @@ export const StaticURLCollection: CollectionConfig = {
slug: 'static-url',
admin: {
livePreview: {
url: '/live-preview/hello-world',
url: '/live-preview/static',
},
},
fields: [

View File

@@ -6,9 +6,9 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { MediaBlock } from './blocks/MediaBlock/index.js'
import { Categories } from './collections/Categories.js'
import { CollectionLevelConfig } from './collections/CollectionLevelConfig.js'
import { ConditionalURL } from './collections/ConditionalURL.js'
import { CustomLivePreview } from './collections/CustomLivePreview.js'
import { Media } from './collections/Media.js'
import { NoURLCollection } from './collections/NoURL.js'
import { Pages } from './collections/Pages.js'
import { Posts } from './collections/Posts.js'
import { SSR } from './collections/SSR.js'
@@ -68,7 +68,7 @@ export default buildConfigWithDefaults({
CollectionLevelConfig,
StaticURLCollection,
CustomLivePreview,
NoURLCollection,
ConditionalURL,
],
globals: [Header, Footer],
onInit: seed,

View File

@@ -179,22 +179,47 @@ describe('Live Preview', () => {
await expect.poll(async () => iframe.getAttribute('src')).toMatch(/\/live-preview/)
})
test('collection — does not render iframe when live preview url is falsy', async () => {
const noURL = new AdminUrlUtil(serverURL, 'no-url')
test('collection — does not render live preview when url is null', async () => {
const noURL = new AdminUrlUtil(serverURL, 'conditional-url')
await page.goto(noURL.create)
await page.locator('#field-title').fill('No URL')
await saveDocAndAssert(page)
// No toggler should render
const toggler = page.locator('button#live-preview-toggler')
await expect(toggler).toBeHidden()
await expect(page.locator('iframe.live-preview-iframe')).toBeHidden()
// Check the `enabled` field
const enabledCheckbox = page.locator('#field-enabled')
await enabledCheckbox.check()
await saveDocAndAssert(page)
// Toggler is present but not iframe
await expect(toggler).toBeVisible()
await toggleLivePreview(page)
await expect(page.locator('iframe.live-preview-iframe')).toBeVisible()
await expect(page.locator('iframe.live-preview-iframe')).toBeHidden()
// Toggle the iframe back on, which will save to prefs
// We need to explicitly test for this, as we don't want live preview to suddenly appear
await toggleLivePreview(page, {
targetState: 'on',
})
// Uncheck the `enabled` field
await enabledCheckbox.uncheck()
await saveDocAndAssert(page)
// Toggler and iframe are gone
await expect(toggler).toBeHidden()
await expect(page.locator('iframe.live-preview-iframe')).toBeHidden()
// Check the `enabled` field
await enabledCheckbox.check()
await saveDocAndAssert(page)
// Toggler is present but still not iframe
await expect(toggler).toBeVisible()
await expect(page.locator('iframe.live-preview-iframe')).toBeHidden()
})
test('collection — retains static URL across edits', async () => {
@@ -204,12 +229,12 @@ describe('Live Preview', () => {
await toggleLivePreview(page, { targetState: 'on' })
const iframe = page.locator('iframe.live-preview-iframe')
await expect.poll(async () => iframe.getAttribute('src')).toMatch(/\/live-preview\/hello/)
await expect.poll(async () => iframe.getAttribute('src')).toMatch(/\/live-preview\/static/)
const titleField = page.locator('#field-title')
await titleField.fill('New Title')
await saveDocAndAssert(page)
await expect.poll(async () => iframe.getAttribute('src')).toMatch(/\/live-preview\/hello/)
await expect.poll(async () => iframe.getAttribute('src')).toMatch(/\/live-preview\/static/)
})
test('collection csr — iframe reflects form state on change', async () => {