feat: adds forceRenderAllFields admin prop to force all fields in edit view to render immediately (#10464)

### What?

Adds new `forceRenderAllFields` `admin` prop to `collection` & `global`
configs.

This new prop forces all fields in the `Edit` view to render
immediately, regardless of scroll position. By default, this is set to
`false` to improve performance, as fields are progressively rendered to
balance load times. Enabling this option can make it easier to locate
fields using browser search (e.g., CMD+F).

```
admin: {
  forceRenderAllFields: true,
},
```

### Why?

Previously, fields were only rendered to a certain viewport pixel height
for performance purposes. As a result, this disallowed using the browser
search on all fields in the edit view if they were not completely loaded
in i.e in the proper viewport.
This commit is contained in:
Patrik
2025-01-09 09:06:06 -05:00
committed by GitHub
parent f430db8bc5
commit 07ff181ccf
23 changed files with 92 additions and 47 deletions

View File

@@ -7,10 +7,10 @@ import type { FieldTypes } from '../../forms/field-types'
import RenderFields from '../../forms/RenderFields'
import { filterFields } from '../../forms/RenderFields/filterFields'
import { useOperation } from '../../utilities/OperationProvider'
import { Gutter } from '../Gutter'
import ViewDescription from '../ViewDescription'
import './index.scss'
import { useOperation } from '../../utilities/OperationProvider'
const baseClass = 'document-fields'
@@ -20,6 +20,7 @@ export const DocumentFields: React.FC<{
description?: Description
fieldTypes: FieldTypes
fields: FieldWithPath[]
forceRenderAllFields?: boolean
forceSidebarWrap?: boolean
hasSavePermission: boolean
permissions: CollectionPermission | GlobalPermission
@@ -30,6 +31,7 @@ export const DocumentFields: React.FC<{
description,
fieldTypes,
fields,
forceRenderAllFields,
forceSidebarWrap,
hasSavePermission,
permissions,
@@ -41,9 +43,9 @@ export const DocumentFields: React.FC<{
fieldSchema: fields,
fieldTypes,
filter: (field) => field?.admin?.position === 'sidebar',
operation,
permissions: permissions.fields,
readOnly: !hasSavePermission,
operation,
})
const hasSidebarFields = sidebarFields && sidebarFields.length > 0
@@ -77,6 +79,7 @@ export const DocumentFields: React.FC<{
!field.admin.position ||
(field.admin.position && field.admin.position !== 'sidebar')
}
forceRenderAllFields={forceRenderAllFields}
permissions={permissions.fields}
readOnly={!hasSavePermission}
/>
@@ -90,6 +93,7 @@ export const DocumentFields: React.FC<{
<RenderFields
fieldTypes={fieldTypes}
fields={sidebarFields}
forceRenderAllFields={forceRenderAllFields}
permissions={permissions.fields}
readOnly={!hasSavePermission}
/>

View File

@@ -33,7 +33,7 @@ const intersectionObserverOptions = {
* All this component does is render the field's Field Components, and pass them the props they need to function.
**/
const RenderFields: React.FC<Props> = (props) => {
const { className, fieldTypes, forceRender, margins } = props
const { className, fieldTypes, forceRender, forceRenderAllFields, margins } = props
const { i18n, t } = useTranslation('general')
const [hasRendered, setHasRendered] = useState(Boolean(forceRender))
@@ -105,7 +105,7 @@ const RenderFields: React.FC<Props> = (props) => {
readOnly,
},
fieldTypes,
forceRender,
forceRender: forceRenderAllFields || forceRender,
indexPath:
'indexPath' in props ? `${props?.indexPath}.${fieldIndex}` : `${fieldIndex}`,
path: field.path || (isFieldAffectingData && 'name' in field ? field.name : ''),

View File

@@ -7,6 +7,7 @@ export type Props = {
className?: string
fieldTypes: FieldTypes
forceRender?: boolean
forceRenderAllFields?: boolean
margins?: 'small' | false
permissions?:
| {

View File

@@ -32,7 +32,8 @@ const ArrayFieldType: React.FC<Props> = (props) => {
admin: { className, components, condition, description, isSortable = true, readOnly },
fieldTypes,
fields,
forceRender = false,
forceRender: forceRenderFromProps = false,
forceRenderAllFields,
indexPath,
localized,
maxRows,
@@ -50,6 +51,8 @@ const ArrayFieldType: React.FC<Props> = (props) => {
const CustomRowLabel = components?.RowLabel || undefined
const forceRender = forceRenderFromProps || forceRenderAllFields
const { setDocFieldPreferences } = useDocumentInfo()
const { addFieldRow, dispatchFields, removeFieldRow, setModified } = useForm()
const submitted = useFormSubmitted()

View File

@@ -5,6 +5,7 @@ import type { ArrayField } from '../../../../../fields/config/types'
export type Props = Omit<ArrayField, 'type'> & {
fieldTypes: FieldTypes
forceRender?: boolean
forceRenderAllFields?: boolean
indexPath: string
label: false | string
path?: string

View File

@@ -37,7 +37,8 @@ const BlocksField: React.FC<Props> = (props) => {
admin: { className, condition, description, isSortable = true, readOnly },
blocks,
fieldTypes,
forceRender = false,
forceRender: forceRenderFromProps = false,
forceRenderAllFields,
indexPath,
label,
labels: labelsFromProps,
@@ -59,6 +60,8 @@ const BlocksField: React.FC<Props> = (props) => {
const drawerSlug = useDrawerSlug('blocks-drawer')
const submitted = useFormSubmitted()
const forceRender = forceRenderFromProps || forceRenderAllFields
const labels = {
plural: t('blocks'),
singular: t('block'),
@@ -118,7 +121,7 @@ const BlocksField: React.FC<Props> = (props) => {
const duplicateRow = useCallback(
(rowIndex: number) => {
dispatchFields({ path, rowIndex, type: 'DUPLICATE_ROW' })
dispatchFields({ type: 'DUPLICATE_ROW', path, rowIndex })
setModified(true)
setTimeout(() => {
@@ -138,7 +141,7 @@ const BlocksField: React.FC<Props> = (props) => {
const moveRow = useCallback(
(moveFromIndex: number, moveToIndex: number) => {
dispatchFields({ moveFromIndex, moveToIndex, path, type: 'MOVE_ROW' })
dispatchFields({ type: 'MOVE_ROW', moveFromIndex, moveToIndex, path })
setModified(true)
},
[dispatchFields, path, setModified],
@@ -146,14 +149,14 @@ const BlocksField: React.FC<Props> = (props) => {
const toggleCollapseAll = useCallback(
(collapsed: boolean) => {
dispatchFields({ collapsed, path, setDocFieldPreferences, type: 'SET_ALL_ROWS_COLLAPSED' })
dispatchFields({ type: 'SET_ALL_ROWS_COLLAPSED', collapsed, path, setDocFieldPreferences })
},
[dispatchFields, path, setDocFieldPreferences],
)
const setCollapse = useCallback(
(rowID: string, collapsed: boolean) => {
dispatchFields({ collapsed, path, rowID, setDocFieldPreferences, type: 'SET_ROW_COLLAPSED' })
dispatchFields({ type: 'SET_ROW_COLLAPSED', collapsed, path, rowID, setDocFieldPreferences })
},
[dispatchFields, path, setDocFieldPreferences],
)

View File

@@ -5,6 +5,7 @@ import type { BlockField } from '../../../../../fields/config/types'
export type Props = Omit<BlockField, 'type'> & {
fieldTypes: FieldTypes
forceRender?: boolean
forceRenderAllFields?: boolean
indexPath: string
path?: string
permissions: FieldPermissions

View File

@@ -26,7 +26,8 @@ const Group: React.FC<Props> = (props) => {
admin: { className, description, hideGutter = false, readOnly, style, width },
fieldTypes,
fields,
forceRender = false,
forceRender: forceRenderFromProps = false,
forceRenderAllFields,
indexPath,
label,
path: pathFromProps,
@@ -44,6 +45,7 @@ const Group: React.FC<Props> = (props) => {
const path = pathFromProps || name
const isTopLevel = !(withinCollapsible || isWithinGroup || isWithinRow)
const forceRender = forceRenderFromProps || forceRenderAllFields
return (
<div

View File

@@ -5,6 +5,7 @@ import type { GroupField } from '../../../../../fields/config/types'
export type Props = Omit<GroupField, 'type'> & {
fieldTypes: FieldTypes
forceRender?: boolean
forceRenderAllFields?: boolean
indexPath: string
path?: string
permissions: FieldPermissions

View File

@@ -16,12 +16,15 @@ const Row: React.FC<Props> = (props) => {
admin: { className, readOnly },
fieldTypes,
fields,
forceRender = false,
forceRender: forceRenderFromProps = false,
forceRenderAllFields,
indexPath,
path,
permissions,
} = props
const forceRender = forceRenderFromProps || forceRenderAllFields
return (
<RowProvider>
<div className={[fieldBaseClass, baseClass, className].filter(Boolean).join(' ')}>

View File

@@ -5,6 +5,7 @@ import type { RowField } from '../../../../../fields/config/types'
export type Props = Omit<RowField, 'type'> & {
fieldTypes: FieldTypes
forceRender?: boolean
forceRenderAllFields?: boolean
indexPath: string
path?: string
permissions: FieldPermissions

View File

@@ -72,7 +72,8 @@ const TabsField: React.FC<Props> = (props) => {
const {
admin: { className, readOnly },
fieldTypes,
forceRender = false,
forceRender: forceRenderFromProps = false,
forceRenderAllFields,
indexPath,
path,
permissions,
@@ -86,6 +87,7 @@ const TabsField: React.FC<Props> = (props) => {
const { withinCollapsible } = useCollapsible()
const [activeTabIndex, setActiveTabIndex] = useState<number>(0)
const tabsPrefKey = `tabs-${indexPath}`
const forceRender = forceRenderFromProps || forceRenderAllFields
useEffect(() => {
if (preferencesKey) {

View File

@@ -5,6 +5,7 @@ import type { TabsField } from '../../../../../fields/config/types'
export type Props = Omit<TabsField, 'type'> & {
fieldTypes: FieldTypes
forceRender?: boolean
forceRenderAllFields?: boolean
indexPath: string
path?: string
permissions: FieldPermissions

View File

@@ -19,7 +19,7 @@ export const ConfigProvider: React.FC<{ children: React.ReactNode; config: Sanit
const resolvedConfig = await incomingConfig
setConfig(resolvedConfig)
}
awaitConfig()
void awaitConfig()
}
}, [incomingConfig])

View File

@@ -19,7 +19,7 @@ export const DefaultGlobalEdit: React.FC<
const { apiURL, data, fieldTypes, global, permissions } = props
const { i18n } = useTranslation()
const { admin: { description } = {}, fields, label } = global
const { admin: { description, forceRenderAllFields } = {}, fields, label } = global
const hasSavePermission = permissions?.update?.permission
@@ -44,6 +44,7 @@ export const DefaultGlobalEdit: React.FC<
description={description}
fieldTypes={fieldTypes}
fields={fields}
forceRenderAllFields={forceRenderAllFields}
hasSavePermission={hasSavePermission}
permissions={permissions}
/>

View File

@@ -122,6 +122,11 @@ const PreviewView: React.FC<
description={description}
fieldTypes={fieldTypes}
fields={fields}
forceRenderAllFields={
collection?.admin?.forceRenderAllFields ??
global?.admin?.forceRenderAllFields ??
false
}
forceSidebarWrap
hasSavePermission={hasSavePermission}
permissions={permissions}

View File

@@ -39,7 +39,7 @@ export const DefaultCollectionEdit: React.FC<
permissions,
} = props
const { auth, upload } = collection
const { admin: { forceRenderAllFields } = {}, auth, upload } = collection
const [fields] = useState(() => formatFields(collection, isEditing))
@@ -92,6 +92,7 @@ export const DefaultCollectionEdit: React.FC<
}
fieldTypes={fieldTypes}
fields={fields}
forceRenderAllFields={forceRenderAllFields}
hasSavePermission={hasSavePermission}
permissions={permissions}
/>

View File

@@ -62,6 +62,7 @@ const collectionSchema = joi.object().keys({
disableDuplicate: joi.bool(),
enableRichTextLink: joi.boolean(),
enableRichTextRelationship: joi.boolean(),
forceRenderAllFields: joi.boolean(),
group: joi.alternatives().try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
hidden: joi.alternatives().try(joi.boolean(), joi.func()),
hideAPIURL: joi.bool(),

View File

@@ -317,6 +317,11 @@ export type CollectionAdminOptions = {
disableDuplicate?: boolean
enableRichTextLink?: boolean
enableRichTextRelationship?: boolean
/**
* Forces all fields in the Edit view to render immediately, regardless of scroll position
* @default false
*/
forceRenderAllFields?: boolean
/**
* Place collections into a navigational group
* */

View File

@@ -40,6 +40,7 @@ const globalSchema = joi
}),
}),
description: joi.alternatives().try(joi.string(), componentSchema),
forceRenderAllFields: joi.boolean(),
group: joi
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),

View File

@@ -138,6 +138,11 @@ export type GlobalAdminOptions = {
* Custom description for collection
*/
description?: EntityDescription
/**
* Forces all fields in the Edit view to render immediately, regardless of scroll position
* @default false
*/
forceRenderAllFields?: boolean
/**
* Place globals into a navigational group
* */