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:
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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 : ''),
|
||||
|
||||
@@ -7,6 +7,7 @@ export type Props = {
|
||||
className?: string
|
||||
fieldTypes: FieldTypes
|
||||
forceRender?: boolean
|
||||
forceRenderAllFields?: boolean
|
||||
margins?: 'small' | false
|
||||
permissions?:
|
||||
| {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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],
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(' ')}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,7 +19,7 @@ export const ConfigProvider: React.FC<{ children: React.ReactNode; config: Sanit
|
||||
const resolvedConfig = await incomingConfig
|
||||
setConfig(resolvedConfig)
|
||||
}
|
||||
awaitConfig()
|
||||
void awaitConfig()
|
||||
}
|
||||
}, [incomingConfig])
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
* */
|
||||
|
||||
@@ -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()])),
|
||||
|
||||
@@ -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
|
||||
* */
|
||||
|
||||
Reference in New Issue
Block a user