import { generateId } from './_helpers'; import type { CustomObject } from './custom-object'; import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context'; import type { ManifestComponentDeletion } from './manifest-builder'; import { requireStringComponentField } from './_existing-component-fields'; export interface CustomObjectFieldSectionLoadFromExistingOptions extends ExistingManifestContextOptions { customObjectApiName?: string; } /** * Initialization properties for {@link CustomObjectFieldSection}. */ export interface CustomObjectFieldSectionProps { /** * Display name of the section shown as a group header on the detail page * (e.g. `'Profile'`, `'Membership'`). * * Required. */ name: string; /** * Stable identifier for this section. Pass a human-readable slug * (e.g. `'sec_gm_profile'`) to keep it stable across manifest regenerations. * * Optional. Auto-generated when omitted. @default generateId('sec') */ sectionId?: string; } /** * Defines a field section and registers it with the manifest. * * A field section is a named group of fields displayed together on a custom * object's detail page. Every custom field must receive one of these sections * through its `section` prop. * * @example * ```ts * const profileSection = new CustomObjectFieldSection(memberObj, { * name: 'Profile', * sectionId: 'sec_gm_profile', * }); * * const firstName = new TextField(memberObj, { * apiName: 'first_name__c', * displayName: 'First name', * section: profileSection, * }); * ``` * * @see {@link CustomObjectPageLayout} — controls which sections appear on which tab. */ export class CustomObjectFieldSection { static readonly componentType = 'CUSTOM_OBJECT_FIELD_SECTION' as const; static toDeletionIdentifier(sectionId: string): ManifestComponentDeletion { return { type: CustomObjectFieldSection.componentType, section_id: sectionId, }; } private readonly _sectionId: string; private readonly _name: string; private readonly _model: CustomObject; /** * @param customObject - The custom object this section belongs to. * @param props - Initialization properties. */ constructor(customObject: CustomObject, props: CustomObjectFieldSectionProps) { this._sectionId = props.sectionId ?? generateId('sec'); this._name = props.name; this._model = customObject; customObject._register(this); } /** * Loads this field section from the active existing-manifest JSON context. */ static loadFromExisting( sectionId: string, options: CustomObjectFieldSectionLoadFromExistingOptions = {}, ): CustomObjectFieldSection { return getExistingManifestContext(options).loadFieldSection(sectionId, options); } /** @internal Hydrates a field section from existing manifest wire JSON. */ static _fromExistingComponent( customObject: CustomObject, component: Record, ): CustomObjectFieldSection { return new CustomObjectFieldSection(customObject, { sectionId: requireStringComponentField(component, 'section_id', CustomObjectFieldSection.componentType), name: requireStringComponentField(component, 'name', CustomObjectFieldSection.componentType), }); } /** * Returns the stable identifier for this section (e.g. `'sec_gm_profile'`). * * Use this value in page layout field entries via `getSectionId()`, or pass * the section instance directly to field `section` props. */ getSectionId(): string { return this._sectionId; } /** * Returns the display name of this field section. */ getName(): string { return this._name; } /** * Returns the api_name of the custom object this section belongs to. */ getModelApiName(): string { return this._model.getApiName(); } /** * Serializes this section to the wire format consumed by the manifest install endpoint. * * @returns A plain object with `type: 'CUSTOM_OBJECT_FIELD_SECTION'`. */ toDict(): Record { return { type: CustomObjectFieldSection.componentType, section_id: this._sectionId, model_name: this._model.getApiName(), name: this._name, }; } }