diff --git a/packages/plugin-multi-tenant/src/components/WatchTenantCollection/index.tsx b/packages/plugin-multi-tenant/src/components/WatchTenantCollection/index.tsx index b20a559b31..4d5bb15874 100644 --- a/packages/plugin-multi-tenant/src/components/WatchTenantCollection/index.tsx +++ b/packages/plugin-multi-tenant/src/components/WatchTenantCollection/index.tsx @@ -2,13 +2,21 @@ import type { ClientCollectionConfig } from 'payload' -import { useConfig, useDocumentInfo, useEffectEvent, useFormFields } from '@payloadcms/ui' +import { + useConfig, + useDocumentInfo, + useDocumentTitle, + useEffectEvent, + useFormFields, +} from '@payloadcms/ui' import React from 'react' import { useTenantSelection } from '../../providers/TenantSelectionProvider/index.client.js' export const WatchTenantCollection = () => { - const { id, collectionSlug, title } = useDocumentInfo() + const { id, collectionSlug } = useDocumentInfo() + const { title } = useDocumentTitle() + const { getEntityConfig } = useConfig() const [useAsTitleName] = React.useState( () => (getEntityConfig({ collectionSlug }) as ClientCollectionConfig).admin.useAsTitle, diff --git a/packages/plugin-seo/src/fields/MetaDescription/MetaDescriptionComponent.tsx b/packages/plugin-seo/src/fields/MetaDescription/MetaDescriptionComponent.tsx index 7b93ff3777..dab8f3f176 100644 --- a/packages/plugin-seo/src/fields/MetaDescription/MetaDescriptionComponent.tsx +++ b/packages/plugin-seo/src/fields/MetaDescription/MetaDescriptionComponent.tsx @@ -8,6 +8,7 @@ import { TextareaInput, useConfig, useDocumentInfo, + useDocumentTitle, useField, useForm, useLocale, @@ -53,6 +54,7 @@ export const MetaDescriptionComponent: React.FC = (props) const locale = useLocale() const { getData } = useForm() const docInfo = useDocumentInfo() + const { title } = useDocumentTitle() const maxLength = maxLengthFromProps || maxLengthDefault const minLength = minLengthFromProps || minLengthDefault @@ -85,7 +87,7 @@ export const MetaDescriptionComponent: React.FC = (props) initialData: docInfo.initialData, initialState: reduceToSerializableFields(docInfo.initialState ?? {}), locale: typeof locale === 'object' ? locale?.code : locale, - title: docInfo.title, + title, } satisfies Omit< Parameters[0], 'collectionConfig' | 'globalConfig' | 'hasPublishedDoc' | 'req' | 'versionCount' @@ -112,10 +114,10 @@ export const MetaDescriptionComponent: React.FC = (props) docInfo.hasSavePermission, docInfo.initialData, docInfo.initialState, - docInfo.title, getData, locale, setValue, + title, ]) return ( diff --git a/packages/plugin-seo/src/fields/MetaImage/MetaImageComponent.tsx b/packages/plugin-seo/src/fields/MetaImage/MetaImageComponent.tsx index 8eca5f7138..b42ee59325 100644 --- a/packages/plugin-seo/src/fields/MetaImage/MetaImageComponent.tsx +++ b/packages/plugin-seo/src/fields/MetaImage/MetaImageComponent.tsx @@ -9,6 +9,7 @@ import { UploadInput, useConfig, useDocumentInfo, + useDocumentTitle, useField, useForm, useLocale, @@ -56,6 +57,8 @@ export const MetaImageComponent: React.FC = (props) => { const { getData } = useForm() const docInfo = useDocumentInfo() + const { title } = useDocumentTitle() + const regenerateImage = useCallback(async () => { if (!hasGenerateImageFn) { return @@ -75,7 +78,7 @@ export const MetaImageComponent: React.FC = (props) => { initialData: docInfo.initialData, initialState: reduceToSerializableFields(docInfo.initialState ?? {}), locale: typeof locale === 'object' ? locale?.code : locale, - title: docInfo.title, + title, } satisfies Omit< Parameters[0], 'collectionConfig' | 'globalConfig' | 'hasPublishedDoc' | 'req' | 'versionCount' @@ -102,10 +105,10 @@ export const MetaImageComponent: React.FC = (props) => { docInfo.hasSavePermission, docInfo.initialData, docInfo.initialState, - docInfo.title, getData, locale, setValue, + title, ]) const hasImage = Boolean(value) diff --git a/packages/plugin-seo/src/fields/MetaTitle/MetaTitleComponent.tsx b/packages/plugin-seo/src/fields/MetaTitle/MetaTitleComponent.tsx index 4d7e57b119..50b347850f 100644 --- a/packages/plugin-seo/src/fields/MetaTitle/MetaTitleComponent.tsx +++ b/packages/plugin-seo/src/fields/MetaTitle/MetaTitleComponent.tsx @@ -8,6 +8,7 @@ import { TextInput, useConfig, useDocumentInfo, + useDocumentTitle, useField, useForm, useLocale, @@ -57,6 +58,7 @@ export const MetaTitleComponent: React.FC = (props) => { const locale = useLocale() const { getData } = useForm() const docInfo = useDocumentInfo() + const { title } = useDocumentTitle() const minLength = minLengthFromProps || minLengthDefault const maxLength = maxLengthFromProps || maxLengthDefault @@ -80,7 +82,7 @@ export const MetaTitleComponent: React.FC = (props) => { initialData: docInfo.initialData, initialState: reduceToSerializableFields(docInfo.initialState ?? {}), locale: typeof locale === 'object' ? locale?.code : locale, - title: docInfo.title, + title, } satisfies Omit< Parameters[0], 'collectionConfig' | 'globalConfig' | 'hasPublishedDoc' | 'req' | 'versionCount' @@ -107,10 +109,10 @@ export const MetaTitleComponent: React.FC = (props) => { docInfo.hasSavePermission, docInfo.initialData, docInfo.initialState, - docInfo.title, getData, locale, setValue, + title, ]) return ( diff --git a/packages/plugin-seo/src/fields/Preview/PreviewComponent.tsx b/packages/plugin-seo/src/fields/Preview/PreviewComponent.tsx index 789e6dd421..c7d7db2efb 100644 --- a/packages/plugin-seo/src/fields/Preview/PreviewComponent.tsx +++ b/packages/plugin-seo/src/fields/Preview/PreviewComponent.tsx @@ -6,6 +6,7 @@ import { useAllFormFields, useConfig, useDocumentInfo, + useDocumentTitle, useForm, useLocale, useTranslation, @@ -42,6 +43,7 @@ export const PreviewComponent: React.FC = (props) => { const [fields] = useAllFormFields() const { getData } = useForm() const docInfo = useDocumentInfo() + const { title } = useDocumentTitle() const descriptionPath = descriptionPathFromContext || 'meta.description' const titlePath = titlePathFromContext || 'meta.title' @@ -69,7 +71,7 @@ export const PreviewComponent: React.FC = (props) => { initialData: docInfo.initialData, initialState: reduceToSerializableFields(docInfo.initialState ?? {}), locale: typeof locale === 'object' ? locale?.code : locale, - title: docInfo.title, + title, } satisfies Omit< Parameters[0], 'collectionConfig' | 'globalConfig' | 'hasPublishedDoc' | 'req' | 'versionCount' @@ -89,7 +91,7 @@ export const PreviewComponent: React.FC = (props) => { if (hasGenerateURLFn && !href) { void getHref() } - }, [fields, href, locale, docInfo, hasGenerateURLFn, getData, serverURL, api]) + }, [fields, href, locale, docInfo, hasGenerateURLFn, getData, serverURL, api, title]) return (
= ({ id, collection, global: globalDoc }) serverURL, }, } = useConfig() + const { docConfig, incrementVersionCount, diff --git a/packages/ui/src/elements/DeleteDocument/index.tsx b/packages/ui/src/elements/DeleteDocument/index.tsx index a53f4e9664..6e9b605762 100644 --- a/packages/ui/src/elements/DeleteDocument/index.tsx +++ b/packages/ui/src/elements/DeleteDocument/index.tsx @@ -12,7 +12,7 @@ import type { DocumentDrawerContextType } from '../DocumentDrawer/Provider.js' import { useForm } from '../../forms/Form/context.js' import { useConfig } from '../../providers/Config/index.js' -import { useDocumentInfo } from '../../providers/DocumentInfo/index.js' +import { useDocumentTitle } from '../../providers/DocumentTitle/index.js' import { useRouteTransition } from '../../providers/RouteTransition/index.js' import { useTranslation } from '../../providers/Translation/index.js' import { requests } from '../../utilities/api.js' @@ -56,7 +56,7 @@ export const DeleteDocument: React.FC = (props) => { const { setModified } = useForm() const router = useRouter() const { i18n, t } = useTranslation() - const { title } = useDocumentInfo() + const { title } = useDocumentTitle() const { startRouteTransition } = useRouteTransition() const { openModal } = useModal() diff --git a/packages/ui/src/elements/DocumentDrawer/DrawerHeader/index.tsx b/packages/ui/src/elements/DocumentDrawer/DrawerHeader/index.tsx index 294950eb8a..7074913a0e 100644 --- a/packages/ui/src/elements/DocumentDrawer/DrawerHeader/index.tsx +++ b/packages/ui/src/elements/DocumentDrawer/DrawerHeader/index.tsx @@ -5,6 +5,7 @@ import { useModal } from '../../../elements/Modal/index.js' import { RenderTitle } from '../../../elements/RenderTitle/index.js' import { XIcon } from '../../../icons/X/index.js' import { useDocumentInfo } from '../../../providers/DocumentInfo/index.js' +import { useDocumentTitle } from '../../../providers/DocumentTitle/index.js' import { useTranslation } from '../../../providers/Translation/index.js' import { IDLabel } from '../../IDLabel/index.js' import { documentDrawerBaseClass } from '../index.js' @@ -37,6 +38,7 @@ export const DocumentDrawerHeader: React.FC<{ } const DocumentTitle: React.FC = () => { - const { id, title } = useDocumentInfo() + const { id } = useDocumentInfo() + const { title } = useDocumentTitle() return id && id !== title ? : null } diff --git a/packages/ui/src/elements/PublishButton/ScheduleDrawer/index.tsx b/packages/ui/src/elements/PublishButton/ScheduleDrawer/index.tsx index c9df66dec6..caa1ee9418 100644 --- a/packages/ui/src/elements/PublishButton/ScheduleDrawer/index.tsx +++ b/packages/ui/src/elements/PublishButton/ScheduleDrawer/index.tsx @@ -17,6 +17,7 @@ import { FieldLabel } from '../../../fields/FieldLabel/index.js' import { Radio } from '../../../fields/RadioGroup/Radio/index.js' import { useConfig } from '../../../providers/Config/index.js' import { useDocumentInfo } from '../../../providers/DocumentInfo/index.js' +import { useDocumentTitle } from '../../../providers/DocumentTitle/index.js' import { useServerFunctions } from '../../../providers/ServerFunctions/index.js' import { useTranslation } from '../../../providers/Translation/index.js' import { requests } from '../../../utilities/api.js' @@ -59,7 +60,8 @@ export const ScheduleDrawer: React.FC = ({ slug, defaultType, schedulePub serverURL, }, } = useConfig() - const { id, collectionSlug, globalSlug, title } = useDocumentInfo() + const { id, collectionSlug, globalSlug } = useDocumentInfo() + const { title } = useDocumentTitle() const { i18n, t } = useTranslation() const { schedulePublish } = useServerFunctions() const [type, setType] = React.useState(defaultType || 'publish') diff --git a/packages/ui/src/elements/RenderTitle/index.tsx b/packages/ui/src/elements/RenderTitle/index.tsx index 82eb5fbcc8..c0e51e5c2d 100644 --- a/packages/ui/src/elements/RenderTitle/index.tsx +++ b/packages/ui/src/elements/RenderTitle/index.tsx @@ -2,6 +2,7 @@ import React, { Fragment } from 'react' import { useDocumentInfo } from '../../providers/DocumentInfo/index.js' +import { useDocumentTitle } from '../../providers/DocumentTitle/index.js' import { IDLabel } from '../IDLabel/index.js' import './index.scss' @@ -18,7 +19,8 @@ export type RenderTitleProps = { export const RenderTitle: React.FC = (props) => { const { className, element = 'h1', fallback, title: titleFromProps } = props - const { id, isInitializing, title: titleFromContext } = useDocumentInfo() + const { id, isInitializing } = useDocumentInfo() + const { title: titleFromContext } = useDocumentTitle() const title = titleFromProps || titleFromContext || fallback diff --git a/packages/ui/src/elements/SaveDraftButton/index.tsx b/packages/ui/src/elements/SaveDraftButton/index.tsx index 509c51c8e0..71534bb330 100644 --- a/packages/ui/src/elements/SaveDraftButton/index.tsx +++ b/packages/ui/src/elements/SaveDraftButton/index.tsx @@ -23,6 +23,7 @@ export function SaveDraftButton(props: SaveDraftButtonClientProps) { serverURL, }, } = useConfig() + const { id, collectionSlug, globalSlug, setUnpublishedVersionCount, uploadStatus } = useDocumentInfo() diff --git a/packages/ui/src/elements/Status/index.tsx b/packages/ui/src/elements/Status/index.tsx index 6e473e8fc5..cd7130e2cd 100644 --- a/packages/ui/src/elements/Status/index.tsx +++ b/packages/ui/src/elements/Status/index.tsx @@ -28,13 +28,16 @@ export const Status: React.FC = () => { setUnpublishedVersionCount, unpublishedVersionCount, } = useDocumentInfo() + const { toggleModal } = useModal() + const { config: { routes: { api }, serverURL, }, } = useConfig() + const { reset: resetForm } = useForm() const { code: locale } = useLocale() const { i18n, t } = useTranslation() diff --git a/packages/ui/src/exports/client/index.ts b/packages/ui/src/exports/client/index.ts index 255e18130f..dbde2fb010 100644 --- a/packages/ui/src/exports/client/index.ts +++ b/packages/ui/src/exports/client/index.ts @@ -277,6 +277,7 @@ export { export { ConfigProvider, useConfig } from '../../providers/Config/index.js' export { DocumentEventsProvider, useDocumentEvents } from '../../providers/DocumentEvents/index.js' export { DocumentInfoProvider, useDocumentInfo } from '../../providers/DocumentInfo/index.js' +export { useDocumentTitle } from '../../providers/DocumentTitle/index.js' export type { DocumentInfoContext, DocumentInfoProps } from '../../providers/DocumentInfo/index.js' export { EditDepthProvider, useEditDepth } from '../../providers/EditDepth/index.js' diff --git a/packages/ui/src/providers/DocumentInfo/index.tsx b/packages/ui/src/providers/DocumentInfo/index.tsx index 7ce902301b..395cb0f064 100644 --- a/packages/ui/src/providers/DocumentInfo/index.tsx +++ b/packages/ui/src/providers/DocumentInfo/index.tsx @@ -4,16 +4,16 @@ import type { ClientUser, DocumentPreferences, SanitizedDocumentPermissions } fr import * as qs from 'qs-esm' import React, { createContext, use, useCallback, useEffect, useMemo, useRef, useState } from 'react' -import type { DocumentInfoContext, DocumentInfoProps } from './types.js' - import { useAuth } from '../../providers/Auth/index.js' import { requests } from '../../utilities/api.js' import { formatDocTitle } from '../../utilities/formatDocTitle/index.js' import { useConfig } from '../Config/index.js' +import { DocumentTitleProvider } from '../DocumentTitle/index.js' import { useLocale, useLocaleLoading } from '../Locale/index.js' import { usePreferences } from '../Preferences/index.js' import { useTranslation } from '../Translation/index.js' import { UploadEditsProvider, useUploadEdits } from '../UploadEdits/index.js' +import { type DocumentInfoContext, type DocumentInfoProps } from './types.js' import { useGetDocPermissions } from './useGetDocPermissions.js' const Context = createContext({} as DocumentInfoContext) @@ -75,7 +75,11 @@ const DocumentInfo: React.FC< const { uploadEdits } = useUploadEdits() - const [documentTitle, setDocumentTitle] = useState(() => + /** + * @deprecated This state will be removed in v4. + * This is for performance reasons. Use the `DocumentTitleContext` instead. + */ + const [title, setDocumentTitle] = useState(() => formatDocTitle({ collectionConfig, data: { ...(initialData || {}), id }, @@ -272,6 +276,10 @@ const DocumentInfo: React.FC< [], ) + /** + * @todo: Remove this in v4 + * Users should use the `DocumentTitleContext` instead. + */ useEffect(() => { setDocumentTitle( formatDocTitle({ @@ -343,7 +351,7 @@ const DocumentInfo: React.FC< setMostRecentVersionIsAutosaved, setUnpublishedVersionCount, setUploadStatus: updateUploadStatus, - title: documentTitle, + title, unlockDocument, unpublishedVersionCount, updateDocumentEditor, @@ -352,7 +360,11 @@ const DocumentInfo: React.FC< versionCount, } - return {children} + return ( + + {children} + + ) } export const DocumentInfoProvider: React.FC< diff --git a/packages/ui/src/providers/DocumentInfo/types.ts b/packages/ui/src/providers/DocumentInfo/types.ts index 6babc9b439..93ec9674e2 100644 --- a/packages/ui/src/providers/DocumentInfo/types.ts +++ b/packages/ui/src/providers/DocumentInfo/types.ts @@ -11,7 +11,8 @@ import type { SanitizedGlobalConfig, TypedUser, } from 'payload' -import type React from 'react' + +import React from 'react' export type DocumentInfoProps = { readonly action?: string @@ -60,12 +61,31 @@ export type DocumentInfoContext = { fieldPreferences: { [key: string]: unknown } & Partial, ) => void setDocumentIsLocked?: React.Dispatch> - setDocumentTitle: (title: string) => void + /** + * + * @deprecated This property is deprecated and will be removed in v4. + * This is for performance reasons. Use the `DocumentTitleContext` instead. + * @example + * ```tsx + * import { useDocumentTitle } from '@payloadcms/ui' + * const { setDocumentTitle } = useDocumentTitle() + * ``` + */ + setDocumentTitle: React.Dispatch> setHasPublishedDoc: React.Dispatch> setLastUpdateTime: React.Dispatch> setMostRecentVersionIsAutosaved: React.Dispatch> setUnpublishedVersionCount: React.Dispatch> setUploadStatus?: (status: 'failed' | 'idle' | 'uploading') => void + /** + * @deprecated This property is deprecated and will be removed in v4. + * This is for performance reasons. Use the `DocumentTitleContext` instead. + * @example + * ```tsx + * import { useDocumentTitle } from '@payloadcms/ui' + * const { title } = useDocumentTitle() + * ``` + */ title: string unlockDocument: (docID: number | string, slug: string) => Promise unpublishedVersionCount: number @@ -74,3 +94,5 @@ export type DocumentInfoContext = { uploadStatus?: 'failed' | 'idle' | 'uploading' versionCount: number } & DocumentInfoProps + +export const DocumentTitleContext = React.createContext('') diff --git a/packages/ui/src/providers/DocumentTitle/context.ts b/packages/ui/src/providers/DocumentTitle/context.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/ui/src/providers/DocumentTitle/index.tsx b/packages/ui/src/providers/DocumentTitle/index.tsx new file mode 100644 index 0000000000..2294219681 --- /dev/null +++ b/packages/ui/src/providers/DocumentTitle/index.tsx @@ -0,0 +1,58 @@ +import type { ClientCollectionConfig, ClientGlobalConfig } from 'payload' + +import { createContext, use, useEffect, useState } from 'react' + +import { formatDocTitle } from '../../utilities/formatDocTitle/index.js' +import { useConfig } from '../Config/index.js' +import { useDocumentInfo } from '../DocumentInfo/index.js' +import { useTranslation } from '../Translation/index.js' + +type IDocumentTitleContext = { + setDocumentTitle: (title: string) => void + title: string +} + +const DocumentTitleContext = createContext({} as IDocumentTitleContext) + +export const useDocumentTitle = (): IDocumentTitleContext => use(DocumentTitleContext) + +export const DocumentTitleProvider: React.FC<{ + children: React.ReactNode +}> = ({ children }) => { + const { id, collectionSlug, docConfig, globalSlug, initialData, savedDocumentData } = + useDocumentInfo() + + const { + config: { + admin: { dateFormat }, + }, + } = useConfig() + + const { i18n } = useTranslation() + + const [title, setDocumentTitle] = useState(() => + formatDocTitle({ + collectionConfig: collectionSlug ? (docConfig as ClientCollectionConfig) : undefined, + data: { ...(initialData || {}), id }, + dateFormat, + fallback: id?.toString(), + globalConfig: globalSlug ? (docConfig as ClientGlobalConfig) : undefined, + i18n, + }), + ) + + useEffect(() => { + setDocumentTitle( + formatDocTitle({ + collectionConfig: collectionSlug ? (docConfig as ClientCollectionConfig) : undefined, + data: { ...savedDocumentData, id }, + dateFormat, + fallback: id?.toString(), + globalConfig: globalSlug ? (docConfig as ClientGlobalConfig) : undefined, + i18n, + }), + ) + }, [savedDocumentData, dateFormat, i18n, id, collectionSlug, docConfig, globalSlug]) + + return {children} +} diff --git a/packages/ui/src/utilities/formatDocTitle/index.ts b/packages/ui/src/utilities/formatDocTitle/index.ts index 76e8ab5ad4..a7214a3412 100644 --- a/packages/ui/src/utilities/formatDocTitle/index.ts +++ b/packages/ui/src/utilities/formatDocTitle/index.ts @@ -45,6 +45,7 @@ export const formatDocTitle = ({ const dateFormat = ('date' in fieldConfig.admin && fieldConfig?.admin?.date?.displayFormat) || dateFormatFromConfig + title = formatDate({ date: title, i18n, pattern: dateFormat }) || title } } @@ -59,6 +60,7 @@ export const formatDocTitle = ({ if (isSerializedLexicalEditor(title)) { title = formatLexicalDocTitle(title.root.children?.[0]?.children || [], '') } + if (!title && isSerializedLexicalEditor(fallback)) { title = formatLexicalDocTitle(fallback.root.children?.[0]?.children || [], '') } diff --git a/packages/ui/src/views/Edit/Auth/index.tsx b/packages/ui/src/views/Edit/Auth/index.tsx index aeb925a1a5..feb88980f8 100644 --- a/packages/ui/src/views/Edit/Auth/index.tsx +++ b/packages/ui/src/views/Edit/Auth/index.tsx @@ -43,6 +43,7 @@ export const Auth: React.FC = (props) => { const modified = useFormModified() const { i18n, t } = useTranslation() const { docPermissions, isEditing, isInitializing } = useDocumentInfo() + const { config: { routes: { api }, diff --git a/packages/ui/src/views/Edit/SetDocumentStepNav/index.tsx b/packages/ui/src/views/Edit/SetDocumentStepNav/index.tsx index 5a914d3f57..5102947af2 100644 --- a/packages/ui/src/views/Edit/SetDocumentStepNav/index.tsx +++ b/packages/ui/src/views/Edit/SetDocumentStepNav/index.tsx @@ -10,6 +10,7 @@ import type { StepNavItem } from '../../../elements/StepNav/index.js' import { useStepNav } from '../../../elements/StepNav/index.js' import { useConfig } from '../../../providers/Config/index.js' import { useDocumentInfo } from '../../../providers/DocumentInfo/index.js' +import { useDocumentTitle } from '../../../providers/DocumentTitle/index.js' import { useEntityVisibility } from '../../../providers/EntityVisibility/index.js' import { useTranslation } from '../../../providers/Translation/index.js' @@ -26,7 +27,9 @@ export const SetDocumentStepNav: React.FC<{ const view: string | undefined = props?.view || undefined - const { isEditing, isInitializing, title } = useDocumentInfo() + const { isEditing, isInitializing } = useDocumentInfo() + const { title } = useDocumentTitle() + const { isEntityVisible } = useEntityVisibility() const isVisible = isEntityVisible({ collectionSlug, globalSlug }) diff --git a/packages/ui/src/views/Edit/SetDocumentTitle/index.tsx b/packages/ui/src/views/Edit/SetDocumentTitle/index.tsx index 045721ffed..fd9662fea2 100644 --- a/packages/ui/src/views/Edit/SetDocumentTitle/index.tsx +++ b/packages/ui/src/views/Edit/SetDocumentTitle/index.tsx @@ -4,7 +4,7 @@ import type { ClientCollectionConfig, ClientConfig, ClientGlobalConfig } from 'p import { useEffect, useRef } from 'react' import { useFormFields } from '../../../forms/Form/context.js' -import { useDocumentInfo } from '../../../providers/DocumentInfo/index.js' +import { useDocumentTitle } from '../../../providers/DocumentTitle/index.js' import { useTranslation } from '../../../providers/Translation/index.js' import { formatDocTitle } from '../../../utilities/formatDocTitle/index.js' @@ -24,7 +24,7 @@ export const SetDocumentTitle: React.FC<{ const { i18n } = useTranslation() - const { setDocumentTitle } = useDocumentInfo() + const { setDocumentTitle } = useDocumentTitle() const dateFormatFromConfig = config?.admin?.dateFormat diff --git a/test/form-state/e2e.spec.ts b/test/form-state/e2e.spec.ts index d44b812ec8..091f5eea4d 100644 --- a/test/form-state/e2e.spec.ts +++ b/test/form-state/e2e.spec.ts @@ -251,6 +251,57 @@ test.describe('Form State', () => { ).toHaveValue('This is a default value') }) + // TODO: This test is not very reliable but would be really nice to have + test.skip('should not lag on slow CPUs', async () => { + await page.goto(postsUrl.create) + + await expect(page.locator('#field-title')).toBeEnabled() + + const cdpSession = await context.newCDPSession(page) + + await cdpSession.send('Emulation.setCPUThrottlingRate', { rate: 25 }) + + // Start measuring input and render times + await page.evaluate(() => { + const inputField = document.querySelector('#field-title') as HTMLInputElement + const logs: Record = {} + + inputField.addEventListener('input', (event) => { + const startTime = performance.now() + + requestAnimationFrame(() => { + const endTime = performance.now() + const elapsedTime = endTime - startTime + logs[event.target?.value] = { elapsedTime } + }) + }) + + window.getLogs = () => logs + }) + + const text = 'This is a test string to measure input lag.' + + await page.locator('#field-title').pressSequentially(text, { delay: 0 }) + + const logs: Record = await page.evaluate(() => + window.getLogs(), + ) + console.log('Logs:', logs) + + const lagTimes = Object.values(logs).map((log) => log.elapsedTime || 0) + + console.log('Lag times:', lagTimes) + + const maxInputLag = Math.max(...lagTimes) + const allowedThreshold = 50 + + expect(maxInputLag).toBeLessThanOrEqual(allowedThreshold) + + // Reset CPU throttling + await cdpSession.send('Emulation.setCPUThrottlingRate', { rate: 1 }) + await cdpSession.detach() + }) + describe('Throttled tests', () => { let cdpSession: CDPSession