@rippling/rippling-sdk 0.2.0-alpha.44 → 0.2.0-alpha.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/manifest/dental-practice-management/dental-practice-management.ts +0 -7
- package/examples/manifest/dental-practice-management/manifest.json +0 -7
- package/examples/manifest/existing-manifest-mutations/existing-manifest-mutations.ts +6 -7
- package/examples/manifest/gym-membership-management/gym-membership-management.ts +0 -7
- package/examples/manifest/gym-membership-management/manifest.json +0 -7
- package/lib/manifest/custom-object-field-section.d.mts +2 -2
- package/lib/manifest/custom-object-field-section.d.ts +2 -2
- package/lib/manifest/custom-object-field-section.js +2 -2
- package/lib/manifest/custom-object-field-section.mjs +2 -2
- package/lib/manifest/custom-object-page-layout.d.mts +78 -37
- package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
- package/lib/manifest/custom-object-page-layout.d.ts +78 -37
- package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
- package/lib/manifest/custom-object-page-layout.js +263 -66
- package/lib/manifest/custom-object-page-layout.js.map +1 -1
- package/lib/manifest/custom-object-page-layout.mjs +263 -66
- package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
- package/lib/manifest/custom-object.d.mts +6 -0
- package/lib/manifest/custom-object.d.mts.map +1 -1
- package/lib/manifest/custom-object.d.ts +6 -0
- package/lib/manifest/custom-object.d.ts.map +1 -1
- package/lib/manifest/custom-object.js +6 -0
- package/lib/manifest/custom-object.js.map +1 -1
- package/lib/manifest/custom-object.mjs +6 -0
- package/lib/manifest/custom-object.mjs.map +1 -1
- package/lib/manifest/existing-manifest-context.d.mts +0 -1
- package/lib/manifest/existing-manifest-context.d.mts.map +1 -1
- package/lib/manifest/existing-manifest-context.d.ts +0 -1
- package/lib/manifest/existing-manifest-context.d.ts.map +1 -1
- package/lib/manifest/existing-manifest-context.js +5 -10
- package/lib/manifest/existing-manifest-context.js.map +1 -1
- package/lib/manifest/existing-manifest-context.mjs +5 -10
- package/lib/manifest/existing-manifest-context.mjs.map +1 -1
- package/lib/manifest/field.d.mts +6 -3
- package/lib/manifest/field.d.mts.map +1 -1
- package/lib/manifest/field.d.ts +6 -3
- package/lib/manifest/field.d.ts.map +1 -1
- package/lib/manifest/field.js +26 -6
- package/lib/manifest/field.js.map +1 -1
- package/lib/manifest/field.mjs +26 -6
- package/lib/manifest/field.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/manifest/custom-object-field-section.ts +2 -2
- package/src/lib/manifest/custom-object-page-layout.ts +477 -93
- package/src/lib/manifest/custom-object.ts +6 -0
- package/src/lib/manifest/existing-manifest-context.ts +5 -12
- package/src/lib/manifest/field.ts +45 -9
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
|
@@ -98,8 +98,6 @@ export interface FieldsSection {
|
|
|
98
98
|
type: 'fields_section';
|
|
99
99
|
/** Field section metadata for the fields displayed in this layout section. Required. */
|
|
100
100
|
section: CustomObjectFieldSection;
|
|
101
|
-
/** Stable layout key for this section. Optional. Defaults to the field section id. */
|
|
102
|
-
key?: string;
|
|
103
101
|
/** Display name of the layout section. Optional. Defaults to the field section name. */
|
|
104
102
|
name?: string;
|
|
105
103
|
/** Fields to display in this section. Required. */
|
|
@@ -227,53 +225,93 @@ export interface VisibilityConditions {
|
|
|
227
225
|
/** Where to insert a new layout item. Defaults to `'end'`. */
|
|
228
226
|
export type PageLayoutMutationPosition = 'start' | 'end' | number;
|
|
229
227
|
|
|
230
|
-
|
|
231
|
-
export interface AddPageLayoutFieldOptions extends SectionFieldOptions {
|
|
232
|
-
/**
|
|
233
|
-
* Tab key containing the target section. Optional when `sectionKey` is unique
|
|
234
|
-
* across the layout.
|
|
235
|
-
*/
|
|
236
|
-
tabKey?: string;
|
|
237
|
-
/** Section key to add the field to. Required. */
|
|
238
|
-
sectionKey: string;
|
|
239
|
-
/**
|
|
240
|
-
* Field-section metadata to assign to the field component.
|
|
241
|
-
*
|
|
242
|
-
* Existing layouts often use layout section keys that differ from field section
|
|
243
|
-
* ids. Pass this when the field was not constructed with a `section`.
|
|
244
|
-
*/
|
|
245
|
-
fieldSection?: CustomObjectFieldSection;
|
|
228
|
+
interface AddPageLayoutFieldOptionsBase extends SectionFieldOptions {
|
|
246
229
|
/** Insert position inside the section field list. Optional. @default `'end'` */
|
|
247
230
|
position?: PageLayoutMutationPosition;
|
|
248
231
|
/** Allow the same field to appear twice in the same section. Optional. @default false */
|
|
249
232
|
allowDuplicate?: boolean;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Options for adding a field to a typed custom-object field section. */
|
|
236
|
+
export interface AddCustomPageLayoutFieldOptions extends AddPageLayoutFieldOptionsBase {
|
|
237
|
+
/**
|
|
238
|
+
* Tab key containing the target section. Optional when `section` is unique
|
|
239
|
+
* across the layout.
|
|
240
|
+
*/
|
|
241
|
+
tabKey?: string;
|
|
242
|
+
/** Field section to add the field to. Required for custom layout sections. */
|
|
243
|
+
section: CustomObjectFieldSection;
|
|
244
|
+
/** Raw section keys are only accepted for built-in system sections. */
|
|
245
|
+
sectionKey?: never;
|
|
246
|
+
/** Deprecated. Use `section`. */
|
|
247
|
+
fieldSection?: never;
|
|
248
|
+
/** Custom layout sections are selected by `section`; `sectionKey` is only for system sections. */
|
|
249
|
+
systemSection?: false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Options for adding a field to a built-in system section. */
|
|
253
|
+
export interface AddSystemPageLayoutFieldOptions extends AddPageLayoutFieldOptionsBase {
|
|
250
254
|
/**
|
|
251
255
|
* Add to a built-in system section via `systemTabEdits`.
|
|
252
256
|
*
|
|
253
|
-
* When true, `tabKey`
|
|
254
|
-
* already exist.
|
|
257
|
+
* When true, `tabKey` and `sectionKey` are required and the edit is created
|
|
258
|
+
* if it does not already exist.
|
|
255
259
|
*/
|
|
256
|
-
systemSection
|
|
260
|
+
systemSection: true;
|
|
261
|
+
/** System tab key containing the target section. Required. */
|
|
262
|
+
tabKey: string;
|
|
263
|
+
/** Built-in system section key. Required. */
|
|
264
|
+
sectionKey: string;
|
|
265
|
+
/** Custom field-section objects are only accepted for custom layout sections. */
|
|
266
|
+
section?: never;
|
|
267
|
+
/** Deprecated. Use `section` for custom sections, or `sectionKey` for system sections. */
|
|
268
|
+
fieldSection?: never;
|
|
257
269
|
}
|
|
258
270
|
|
|
271
|
+
/** Options for adding a field to an existing layout section. */
|
|
272
|
+
export type AddPageLayoutFieldOptions = AddCustomPageLayoutFieldOptions | AddSystemPageLayoutFieldOptions;
|
|
273
|
+
|
|
259
274
|
/** Options for adding multiple fields to an existing layout section. */
|
|
260
275
|
export type AddPageLayoutFieldsOptions = AddPageLayoutFieldOptions;
|
|
261
276
|
|
|
262
277
|
/** Options for removing fields from a layout. */
|
|
263
|
-
|
|
264
|
-
/** Limit removal to one tab. Optional. */
|
|
265
|
-
tabKey?: string;
|
|
266
|
-
/** Limit removal to one section. Optional. */
|
|
267
|
-
sectionKey?: string;
|
|
278
|
+
interface RemovePageLayoutFieldOptionsBase {
|
|
268
279
|
/**
|
|
269
280
|
* Remove all matching occurrences. When false, removing a field that appears
|
|
270
281
|
* more than once throws. Optional. @default true
|
|
271
282
|
*/
|
|
272
283
|
removeAll?: boolean;
|
|
273
|
-
/** Remove from a built-in system section edit. Optional. */
|
|
274
|
-
systemSection?: boolean;
|
|
275
284
|
}
|
|
276
285
|
|
|
286
|
+
/** Options for removing fields from typed custom-object field sections. */
|
|
287
|
+
export interface RemoveCustomPageLayoutFieldOptions extends RemovePageLayoutFieldOptionsBase {
|
|
288
|
+
/** Limit removal to one tab. Optional. */
|
|
289
|
+
tabKey?: string;
|
|
290
|
+
/** Limit removal to one field section. Optional. */
|
|
291
|
+
section?: CustomObjectFieldSection;
|
|
292
|
+
/** Raw section keys are only accepted for built-in system sections. */
|
|
293
|
+
sectionKey?: never;
|
|
294
|
+
/** Custom layout sections are selected by `section`; `sectionKey` is only for system sections. */
|
|
295
|
+
systemSection?: false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Options for removing fields from built-in system section edits. */
|
|
299
|
+
export interface RemoveSystemPageLayoutFieldOptions extends RemovePageLayoutFieldOptionsBase {
|
|
300
|
+
/** Remove from a built-in system section edit. Required. */
|
|
301
|
+
systemSection: true;
|
|
302
|
+
/** System tab key containing the target section. Required. */
|
|
303
|
+
tabKey: string;
|
|
304
|
+
/** Built-in system section key. Required. */
|
|
305
|
+
sectionKey: string;
|
|
306
|
+
/** Custom field-section objects are only accepted for custom layout sections. */
|
|
307
|
+
section?: never;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Options for removing fields from a layout. */
|
|
311
|
+
export type RemovePageLayoutFieldOptions =
|
|
312
|
+
| RemoveCustomPageLayoutFieldOptions
|
|
313
|
+
| RemoveSystemPageLayoutFieldOptions;
|
|
314
|
+
|
|
277
315
|
/** Options for adding a section to an existing tab. */
|
|
278
316
|
export interface AddPageLayoutSectionOptions {
|
|
279
317
|
/** Tab key to add the section to. Required. */
|
|
@@ -299,8 +337,16 @@ export interface RemovePageLayoutSectionOptions {
|
|
|
299
337
|
* more than once throws. Optional. @default true
|
|
300
338
|
*/
|
|
301
339
|
removeAll?: boolean;
|
|
302
|
-
/**
|
|
303
|
-
systemSection?:
|
|
340
|
+
/** Custom layout sections are selected by `CustomObjectFieldSection`; raw keys are only for system sections. */
|
|
341
|
+
systemSection?: false;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** Options for removing a built-in system section from a layout. */
|
|
345
|
+
export interface RemoveSystemPageLayoutSectionOptions {
|
|
346
|
+
/** System tab key containing the target section. Required. */
|
|
347
|
+
tabKey: string;
|
|
348
|
+
/** Delete a built-in system section via `systemSectionEdits`. Required. */
|
|
349
|
+
systemSection: true;
|
|
304
350
|
}
|
|
305
351
|
|
|
306
352
|
/** Options for adding a tab to a layout. */
|
|
@@ -409,7 +455,6 @@ interface SerializedSectionField extends SectionFieldOptions {
|
|
|
409
455
|
|
|
410
456
|
interface SerializedFieldsSection {
|
|
411
457
|
type: 'fields_section';
|
|
412
|
-
key: string;
|
|
413
458
|
name: string;
|
|
414
459
|
fields: SerializedSectionField[];
|
|
415
460
|
layout?: SectionFieldsLayout;
|
|
@@ -459,6 +504,44 @@ function isFieldRef(value: SectionField): value is SectionFieldRef {
|
|
|
459
504
|
return candidate != null && typeof candidate === 'object' && isField(candidate.field);
|
|
460
505
|
}
|
|
461
506
|
|
|
507
|
+
const FIELD_SECTION_ID = Symbol('CustomObjectPageLayout.fieldSectionId');
|
|
508
|
+
|
|
509
|
+
function isCustomObjectFieldSection(value: unknown): value is CustomObjectFieldSection {
|
|
510
|
+
return value instanceof CustomObjectFieldSection;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function attachFieldSectionId(section: SerializedFieldsSection, sectionId: string): SerializedFieldsSection {
|
|
514
|
+
Object.defineProperty(section, FIELD_SECTION_ID, {
|
|
515
|
+
value: sectionId,
|
|
516
|
+
enumerable: false,
|
|
517
|
+
configurable: true,
|
|
518
|
+
});
|
|
519
|
+
return section;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function layoutSectionKey(section: SerializedFieldsSection): string | undefined {
|
|
523
|
+
return (section as unknown as Record<typeof FIELD_SECTION_ID, string | undefined>)[FIELD_SECTION_ID];
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function layoutSectionMatches(section: SerializedFieldsSection, sectionKey: string): boolean {
|
|
527
|
+
return layoutSectionKey(section) === sectionKey;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function inputFieldsSectionHasKey(input: PageLayoutSection): boolean {
|
|
531
|
+
const record = input as unknown as Record<string, unknown>;
|
|
532
|
+
return Object.prototype.hasOwnProperty.call(record, 'key') && record['key'] != null;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function assertNoSerializedSectionKey(section: SerializedFieldsSection): void {
|
|
536
|
+
const record = section as unknown as Record<string, unknown>;
|
|
537
|
+
if (Object.prototype.hasOwnProperty.call(record, 'key') && record['key'] != null) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
'CustomObjectPageLayout loaded fields_section key is not supported. ' +
|
|
540
|
+
'Remove key and target the section with CustomObjectFieldSection metadata.',
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
462
545
|
function assertSameObject(customObject: CustomObject, field: Field, sectionKey: string): void {
|
|
463
546
|
if (field.getCustomObjectApiName() !== customObject.getApiName()) {
|
|
464
547
|
throw new Error(
|
|
@@ -477,6 +560,22 @@ function assertSectionBelongsTo(customObject: CustomObject, section: CustomObjec
|
|
|
477
560
|
}
|
|
478
561
|
}
|
|
479
562
|
|
|
563
|
+
function requireFieldSectionOption(value: unknown, label: string): CustomObjectFieldSection {
|
|
564
|
+
if (isCustomObjectFieldSection(value)) return value;
|
|
565
|
+
if (typeof value === 'string') {
|
|
566
|
+
throw new TypeError(
|
|
567
|
+
`${label} must be a CustomObjectFieldSection object; received raw section id "${value}". ` +
|
|
568
|
+
'Load/include the existing field section first.',
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
throw new TypeError(`${label} must be a CustomObjectFieldSection object.`);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function assertOptionAbsent(options: unknown, key: string, message: string): void {
|
|
575
|
+
if (options == null || typeof options !== 'object') return;
|
|
576
|
+
if (Object.prototype.hasOwnProperty.call(options, key)) throw new TypeError(message);
|
|
577
|
+
}
|
|
578
|
+
|
|
480
579
|
function toSerializedSectionField(
|
|
481
580
|
fieldRqlName: string,
|
|
482
581
|
options: SectionFieldOptions = {},
|
|
@@ -517,16 +616,18 @@ function normalizeFieldsSection(
|
|
|
517
616
|
input: PageLayoutSection,
|
|
518
617
|
): SerializedFieldsSection {
|
|
519
618
|
assertSectionBelongsTo(customObject, input.section);
|
|
520
|
-
const
|
|
619
|
+
const sectionId = input.section.getSectionId();
|
|
620
|
+
if (inputFieldsSectionHasKey(input)) {
|
|
621
|
+
throw new Error('CustomObjectPageLayout fields_section key is not supported. Omit key.');
|
|
622
|
+
}
|
|
521
623
|
const name = input.name ?? input.section.getName();
|
|
522
624
|
const normalized: SerializedFieldsSection = {
|
|
523
|
-
key,
|
|
524
625
|
name,
|
|
525
626
|
type: 'fields_section',
|
|
526
|
-
fields: input.fields.map((field) => normalizeSectionField(customObject, input.section,
|
|
627
|
+
fields: input.fields.map((field) => normalizeSectionField(customObject, input.section, sectionId, field)),
|
|
527
628
|
};
|
|
528
629
|
if (input.layout != null) normalized.layout = input.layout;
|
|
529
|
-
return normalized;
|
|
630
|
+
return attachFieldSectionId(normalized, sectionId);
|
|
530
631
|
}
|
|
531
632
|
|
|
532
633
|
function normalizeTab(customObject: CustomObject, tab: PageLayoutTab): SerializedPageLayoutTab {
|
|
@@ -599,11 +700,69 @@ function cloneJson<T>(value: T): T {
|
|
|
599
700
|
return JSON.parse(JSON.stringify(value)) as T;
|
|
600
701
|
}
|
|
601
702
|
|
|
703
|
+
const HEADER_EDIT_KEYS = new Set([
|
|
704
|
+
'newTitleField',
|
|
705
|
+
'titleFieldDeleted',
|
|
706
|
+
'newDescriptionField',
|
|
707
|
+
'descriptionFieldDeleted',
|
|
708
|
+
'systemFieldEdits',
|
|
709
|
+
'newFields',
|
|
710
|
+
'headerFieldsOrder',
|
|
711
|
+
'buttons',
|
|
712
|
+
]);
|
|
713
|
+
|
|
714
|
+
const SYSTEM_HEADER_FIELD_EDIT_KEYS = new Set(['newRqlField', 'deleted']);
|
|
715
|
+
|
|
716
|
+
const NEW_HEADER_FIELD_KEYS = new Set(['rqlName', 'key', 'canBeDeleted', 'canBeChanged', 'canBeMoved']);
|
|
717
|
+
|
|
718
|
+
function assertKnownKeys(
|
|
719
|
+
record: Record<string, unknown>,
|
|
720
|
+
allowedKeys: ReadonlySet<string>,
|
|
721
|
+
label: string,
|
|
722
|
+
hint: string,
|
|
723
|
+
): void {
|
|
724
|
+
for (const key of Object.keys(record)) {
|
|
725
|
+
if (!allowedKeys.has(key)) {
|
|
726
|
+
throw new Error(`${label} contains unsupported key "${key}". ${hint}`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function normalizeHeaderEdits(headerEdits: HeaderEdits): HeaderEdits {
|
|
732
|
+
assertKnownKeys(
|
|
733
|
+
headerEdits as unknown as Record<string, unknown>,
|
|
734
|
+
HEADER_EDIT_KEYS,
|
|
735
|
+
'CustomObjectPageLayout headerEdits',
|
|
736
|
+
'Use addHeaderField(), setTitleField(), setDescriptionField(), or the HeaderEdits keys documented by the SDK.',
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
for (const [key, edit] of Object.entries(headerEdits.systemFieldEdits ?? {})) {
|
|
740
|
+
assertKnownKeys(
|
|
741
|
+
edit as Record<string, unknown>,
|
|
742
|
+
SYSTEM_HEADER_FIELD_EDIT_KEYS,
|
|
743
|
+
`CustomObjectPageLayout headerEdits.systemFieldEdits.${key}`,
|
|
744
|
+
'Use newRqlField and deleted for system header field edits.',
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
for (const [index, field] of (headerEdits.newFields ?? []).entries()) {
|
|
749
|
+
assertKnownKeys(
|
|
750
|
+
field as unknown as Record<string, unknown>,
|
|
751
|
+
NEW_HEADER_FIELD_KEYS,
|
|
752
|
+
`CustomObjectPageLayout headerEdits.newFields[${index}]`,
|
|
753
|
+
'Use { rqlName, key } entries, or call addHeaderField(field).',
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return headerEdits;
|
|
758
|
+
}
|
|
759
|
+
|
|
602
760
|
type MutableSectionField = SerializedSectionField | Record<string, any> | string;
|
|
603
761
|
|
|
604
762
|
interface MutableFieldContainer {
|
|
605
763
|
tabKey: string;
|
|
606
|
-
sectionKey: string;
|
|
764
|
+
sectionKey: string | undefined;
|
|
765
|
+
section?: SerializedFieldsSection;
|
|
607
766
|
fields: MutableSectionField[];
|
|
608
767
|
label: string;
|
|
609
768
|
}
|
|
@@ -633,6 +792,61 @@ function fieldRqlNameOf(entry: unknown): string | undefined {
|
|
|
633
792
|
return typeof fieldRqlName === 'string' ? fieldRqlName : undefined;
|
|
634
793
|
}
|
|
635
794
|
|
|
795
|
+
function explicitFieldSectionId(entry: unknown): string | undefined {
|
|
796
|
+
if (entry == null || typeof entry !== 'object') return undefined;
|
|
797
|
+
const record = entry as Record<string, any>;
|
|
798
|
+
const fieldSectionId = record['fieldSection'] ?? record['field_section'];
|
|
799
|
+
return typeof fieldSectionId === 'string' ? fieldSectionId : undefined;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function inferredSectionIdFromFields(
|
|
803
|
+
section: SerializedFieldsSection,
|
|
804
|
+
resolveFieldSectionId?: (fieldApiName: string) => string | undefined,
|
|
805
|
+
): string | undefined {
|
|
806
|
+
const sectionIds = new Set<string>();
|
|
807
|
+
for (const field of section.fields ?? []) {
|
|
808
|
+
const explicitSectionId = explicitFieldSectionId(field);
|
|
809
|
+
if (explicitSectionId != null) {
|
|
810
|
+
sectionIds.add(explicitSectionId);
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const fieldApiName = fieldRqlNameOf(field);
|
|
815
|
+
if (fieldApiName == null || resolveFieldSectionId == null) continue;
|
|
816
|
+
const resolvedSectionId = resolveFieldSectionId(fieldApiName);
|
|
817
|
+
if (resolvedSectionId != null) sectionIds.add(resolvedSectionId);
|
|
818
|
+
}
|
|
819
|
+
return sectionIds.size === 1 ? [...sectionIds][0] : undefined;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function annotateLoadedFieldsSection(
|
|
823
|
+
section: SerializedFieldsSection,
|
|
824
|
+
resolveFieldSectionId?: (fieldApiName: string) => string | undefined,
|
|
825
|
+
): SerializedFieldsSection {
|
|
826
|
+
assertNoSerializedSectionKey(section);
|
|
827
|
+
const sectionId = layoutSectionKey(section) ?? inferredSectionIdFromFields(section, resolveFieldSectionId);
|
|
828
|
+
return sectionId == null ? section : attachFieldSectionId(section, sectionId);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function annotateLoadedTabEdits(
|
|
832
|
+
tabEdits: SerializedTabEdits,
|
|
833
|
+
resolveFieldSectionId?: (fieldApiName: string) => string | undefined,
|
|
834
|
+
): SerializedTabEdits {
|
|
835
|
+
for (const tab of tabEdits.newTabs ?? []) {
|
|
836
|
+
if (!isTabWithSections(tab)) continue;
|
|
837
|
+
tab.sections = tab.sections.map((section) => annotateLoadedFieldsSection(section, resolveFieldSectionId));
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
for (const tabEdit of Object.values(tabEdits.systemTabEdits ?? {})) {
|
|
841
|
+
if (tabEdit.newSections != null) {
|
|
842
|
+
tabEdit.newSections = tabEdit.newSections.map((section) =>
|
|
843
|
+
annotateLoadedFieldsSection(section, resolveFieldSectionId),
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return tabEdits;
|
|
848
|
+
}
|
|
849
|
+
|
|
636
850
|
function removeIndexes<T>(items: T[], indexes: number[]): void {
|
|
637
851
|
for (const index of [...indexes].sort((a, b) => b - a)) {
|
|
638
852
|
items.splice(index, 1);
|
|
@@ -715,7 +929,6 @@ function ensureArrayProperty<T>(record: Record<string, any>, key: string, label:
|
|
|
715
929
|
* newTabs: [{
|
|
716
930
|
* key: 'member', name: 'Member', type: 'tab_with_sections',
|
|
717
931
|
* sections: [{
|
|
718
|
-
* key: 'profile',
|
|
719
932
|
* name: 'Profile',
|
|
720
933
|
* type: 'fields_section',
|
|
721
934
|
* section: profileSection,
|
|
@@ -764,7 +977,7 @@ export class CustomObjectPageLayout {
|
|
|
764
977
|
this._customObjectApiName = customObject.getApiName();
|
|
765
978
|
this._name = props.name;
|
|
766
979
|
this._tabEdits = normalizeTabEdits(customObject, props.tabEdits ?? {});
|
|
767
|
-
this._headerEdits = props.headerEdits ?? {};
|
|
980
|
+
this._headerEdits = normalizeHeaderEdits(props.headerEdits ?? {});
|
|
768
981
|
this._visibilityConditions = props.visibilityConditions ?? {};
|
|
769
982
|
this._blueprintKey = props.blueprintKey;
|
|
770
983
|
customObject._register(this);
|
|
@@ -788,6 +1001,7 @@ export class CustomObjectPageLayout {
|
|
|
788
1001
|
static _fromExistingComponent(
|
|
789
1002
|
customObject: CustomObject,
|
|
790
1003
|
component: Record<string, any>,
|
|
1004
|
+
resolveFieldSectionId?: (fieldApiName: string) => string | undefined,
|
|
791
1005
|
): CustomObjectPageLayout {
|
|
792
1006
|
const layout = Object.create(CustomObjectPageLayout.prototype) as CustomObjectPageLayout;
|
|
793
1007
|
Object.assign(layout as Record<string, any>, {
|
|
@@ -799,7 +1013,7 @@ export class CustomObjectPageLayout {
|
|
|
799
1013
|
CustomObjectPageLayout.componentType,
|
|
800
1014
|
),
|
|
801
1015
|
_name: requireStringComponentField(component, 'name', CustomObjectPageLayout.componentType),
|
|
802
|
-
_tabEdits: cloneJson(component['tab_edits'] ?? {}),
|
|
1016
|
+
_tabEdits: annotateLoadedTabEdits(cloneJson(component['tab_edits'] ?? {}), resolveFieldSectionId),
|
|
803
1017
|
_headerEdits: cloneJson(component['header_edits'] ?? {}),
|
|
804
1018
|
_visibilityConditions: cloneJson(component['visibility_conditions'] ?? {}),
|
|
805
1019
|
_blueprintKey: component['blueprint_key'],
|
|
@@ -842,17 +1056,59 @@ export class CustomObjectPageLayout {
|
|
|
842
1056
|
/**
|
|
843
1057
|
* Adds multiple fields to an existing layout section.
|
|
844
1058
|
*
|
|
845
|
-
* Fields are inserted in the order provided.
|
|
846
|
-
*
|
|
847
|
-
* `CUSTOM_OBJECT_FIELD_SECTION
|
|
1059
|
+
* Fields are inserted in the order provided. For custom layout sections,
|
|
1060
|
+
* pass `options.section` so TypeScript and runtime validation agree on the
|
|
1061
|
+
* target `CUSTOM_OBJECT_FIELD_SECTION`.
|
|
848
1062
|
*/
|
|
849
1063
|
addFields(fields: Field[], options: AddPageLayoutFieldsOptions): this {
|
|
850
1064
|
if (fields.length === 0) return this;
|
|
1065
|
+
|
|
1066
|
+
let fieldSectionForMutation: CustomObjectFieldSection | undefined;
|
|
851
1067
|
const container =
|
|
852
1068
|
options.systemSection === true ?
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1069
|
+
(() => {
|
|
1070
|
+
assertOptionAbsent(
|
|
1071
|
+
options,
|
|
1072
|
+
'section',
|
|
1073
|
+
'CustomObjectPageLayout addField section is only supported for custom layout sections.',
|
|
1074
|
+
);
|
|
1075
|
+
assertOptionAbsent(
|
|
1076
|
+
options,
|
|
1077
|
+
'fieldSection',
|
|
1078
|
+
'CustomObjectPageLayout addField fieldSection is no longer supported. ' +
|
|
1079
|
+
'Use sectionKey only for systemSection: true.',
|
|
1080
|
+
);
|
|
1081
|
+
return this.ensureSystemSectionFieldContainer(options.tabKey, options.sectionKey);
|
|
1082
|
+
})()
|
|
1083
|
+
: (() => {
|
|
1084
|
+
assertOptionAbsent(
|
|
1085
|
+
options,
|
|
1086
|
+
'sectionKey',
|
|
1087
|
+
'CustomObjectPageLayout addField sectionKey is only supported with systemSection: true. ' +
|
|
1088
|
+
'Pass section: CustomObjectFieldSection for custom layout sections.',
|
|
1089
|
+
);
|
|
1090
|
+
assertOptionAbsent(
|
|
1091
|
+
options,
|
|
1092
|
+
'fieldSection',
|
|
1093
|
+
'CustomObjectPageLayout addField fieldSection is no longer supported. ' +
|
|
1094
|
+
'Pass section: CustomObjectFieldSection instead.',
|
|
1095
|
+
);
|
|
1096
|
+
const section = requireFieldSectionOption(
|
|
1097
|
+
options.section,
|
|
1098
|
+
'CustomObjectPageLayout addField section',
|
|
1099
|
+
);
|
|
1100
|
+
this.assertFieldSectionBelongsToLayout(section);
|
|
1101
|
+
fieldSectionForMutation = section;
|
|
1102
|
+
return this.requireOneFieldContainer(options.tabKey, section.getSectionId(), section);
|
|
1103
|
+
})();
|
|
1104
|
+
const entries = fields.map((field) =>
|
|
1105
|
+
this.serializeFieldForMutation(
|
|
1106
|
+
field,
|
|
1107
|
+
container.sectionKey ?? container.label,
|
|
1108
|
+
options,
|
|
1109
|
+
fieldSectionForMutation,
|
|
1110
|
+
),
|
|
1111
|
+
);
|
|
856
1112
|
|
|
857
1113
|
if (options.allowDuplicate !== true) {
|
|
858
1114
|
const existing = new Set(container.fields.map((entry) => fieldRqlNameOf(entry)).filter(Boolean));
|
|
@@ -883,9 +1139,34 @@ export class CustomObjectPageLayout {
|
|
|
883
1139
|
const fieldApiName = typeof field === 'string' ? field : field.getApiName();
|
|
884
1140
|
if (isField(field)) this.assertFieldBelongsToLayout(field);
|
|
885
1141
|
|
|
1142
|
+
let sectionKey: string | undefined;
|
|
1143
|
+
if (options.systemSection === true) {
|
|
1144
|
+
assertOptionAbsent(
|
|
1145
|
+
options,
|
|
1146
|
+
'section',
|
|
1147
|
+
'CustomObjectPageLayout removeField section is only supported for custom layout sections.',
|
|
1148
|
+
);
|
|
1149
|
+
sectionKey = options.sectionKey;
|
|
1150
|
+
} else {
|
|
1151
|
+
assertOptionAbsent(
|
|
1152
|
+
options,
|
|
1153
|
+
'sectionKey',
|
|
1154
|
+
'CustomObjectPageLayout removeField sectionKey is only supported with systemSection: true. ' +
|
|
1155
|
+
'Pass section: CustomObjectFieldSection for custom layout sections.',
|
|
1156
|
+
);
|
|
1157
|
+
if (options.section != null) {
|
|
1158
|
+
const section = requireFieldSectionOption(
|
|
1159
|
+
options.section,
|
|
1160
|
+
'CustomObjectPageLayout removeField section',
|
|
1161
|
+
);
|
|
1162
|
+
this.assertFieldSectionBelongsToLayout(section);
|
|
1163
|
+
sectionKey = section.getSectionId();
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
886
1167
|
const containers = this.findFieldContainers({
|
|
887
1168
|
tabKey: options.tabKey,
|
|
888
|
-
sectionKey
|
|
1169
|
+
sectionKey,
|
|
889
1170
|
systemSectionOnly: options.systemSection === true,
|
|
890
1171
|
});
|
|
891
1172
|
const matches: Array<{ container: MutableFieldContainer; index: number }> = [];
|
|
@@ -944,9 +1225,13 @@ export class CustomObjectPageLayout {
|
|
|
944
1225
|
'newSections',
|
|
945
1226
|
`systemTabEdits.${options.tabKey}.newSections`,
|
|
946
1227
|
);
|
|
947
|
-
this.assertSectionKeyAvailable(
|
|
1228
|
+
this.assertSectionKeyAvailable(
|
|
1229
|
+
sections,
|
|
1230
|
+
layoutSectionKey(normalized),
|
|
1231
|
+
options.tabKey,
|
|
1232
|
+
options.allowDuplicate,
|
|
1233
|
+
);
|
|
948
1234
|
insertAt(sections, normalized, options.position);
|
|
949
|
-
if (tabEdit.sectionsOrder != null) insertAt(tabEdit.sectionsOrder, normalized.key, options.position);
|
|
950
1235
|
return this;
|
|
951
1236
|
}
|
|
952
1237
|
|
|
@@ -957,7 +1242,12 @@ export class CustomObjectPageLayout {
|
|
|
957
1242
|
'Pass systemTab: true to add a section to a built-in system tab.',
|
|
958
1243
|
);
|
|
959
1244
|
}
|
|
960
|
-
this.assertSectionKeyAvailable(
|
|
1245
|
+
this.assertSectionKeyAvailable(
|
|
1246
|
+
tab.sections,
|
|
1247
|
+
layoutSectionKey(normalized),
|
|
1248
|
+
options.tabKey,
|
|
1249
|
+
options.allowDuplicate,
|
|
1250
|
+
);
|
|
961
1251
|
insertAt(tab.sections, normalized, options.position);
|
|
962
1252
|
return this;
|
|
963
1253
|
}
|
|
@@ -968,19 +1258,33 @@ export class CustomObjectPageLayout {
|
|
|
968
1258
|
* Pass `systemSection: true` with `tabKey` to delete a built-in section via
|
|
969
1259
|
* `systemSectionEdits[sectionKey].deleted`.
|
|
970
1260
|
*/
|
|
971
|
-
removeSection(
|
|
1261
|
+
removeSection(section: CustomObjectFieldSection, options?: RemovePageLayoutSectionOptions): this;
|
|
1262
|
+
removeSection(sectionKey: string, options: RemoveSystemPageLayoutSectionOptions): this;
|
|
1263
|
+
removeSection(
|
|
1264
|
+
sectionOrKey: CustomObjectFieldSection | string,
|
|
1265
|
+
options: RemovePageLayoutSectionOptions | RemoveSystemPageLayoutSectionOptions = {},
|
|
1266
|
+
): this {
|
|
972
1267
|
if (options.systemSection === true) {
|
|
1268
|
+
if (typeof sectionOrKey !== 'string') {
|
|
1269
|
+
throw new TypeError(
|
|
1270
|
+
'CustomObjectPageLayout removeSection with systemSection: true requires a system section key.',
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
973
1273
|
if (options.tabKey == null) {
|
|
974
1274
|
throw new Error('CustomObjectPageLayout removeSection with systemSection: true requires tabKey.');
|
|
975
1275
|
}
|
|
976
|
-
const sectionEdit = this.ensureSystemSectionEdit(options.tabKey,
|
|
1276
|
+
const sectionEdit = this.ensureSystemSectionEdit(options.tabKey, sectionOrKey);
|
|
977
1277
|
sectionEdit.deleted = true;
|
|
978
|
-
this.removeSectionVisibility(
|
|
1278
|
+
this.removeSectionVisibility(sectionOrKey);
|
|
979
1279
|
return this;
|
|
980
1280
|
}
|
|
981
1281
|
|
|
1282
|
+
const section = requireFieldSectionOption(sectionOrKey, 'CustomObjectPageLayout removeSection section');
|
|
1283
|
+
this.assertFieldSectionBelongsToLayout(section);
|
|
1284
|
+
const sectionKey = section.getSectionId();
|
|
982
1285
|
const matches: Array<{
|
|
983
1286
|
sections: SerializedFieldsSection[];
|
|
1287
|
+
section: SerializedFieldsSection;
|
|
984
1288
|
index: number;
|
|
985
1289
|
tabEdit?: SerializedSystemTabEdit;
|
|
986
1290
|
tabKey: string;
|
|
@@ -989,14 +1293,15 @@ export class CustomObjectPageLayout {
|
|
|
989
1293
|
if (!isTabWithSections(tab)) continue;
|
|
990
1294
|
if (options.tabKey != null && tab.key !== options.tabKey) continue;
|
|
991
1295
|
tab.sections.forEach((section, index) => {
|
|
992
|
-
if (section
|
|
1296
|
+
if (layoutSectionMatches(section, sectionKey))
|
|
1297
|
+
matches.push({ sections: tab.sections, section, index, tabKey: tab.key });
|
|
993
1298
|
});
|
|
994
1299
|
}
|
|
995
1300
|
for (const [tabKey, tabEdit] of Object.entries(this._tabEdits.systemTabEdits ?? {})) {
|
|
996
1301
|
if (options.tabKey != null && tabKey !== options.tabKey) continue;
|
|
997
1302
|
(tabEdit.newSections ?? []).forEach((section, index) => {
|
|
998
|
-
if (section
|
|
999
|
-
matches.push({ sections: tabEdit.newSections!, index, tabEdit, tabKey });
|
|
1303
|
+
if (layoutSectionMatches(section, sectionKey))
|
|
1304
|
+
matches.push({ sections: tabEdit.newSections!, section, index, tabEdit, tabKey });
|
|
1000
1305
|
});
|
|
1001
1306
|
}
|
|
1002
1307
|
|
|
@@ -1018,11 +1323,11 @@ export class CustomObjectPageLayout {
|
|
|
1018
1323
|
indexes.push(match.index);
|
|
1019
1324
|
byArray.set(match.sections, indexes);
|
|
1020
1325
|
if (match.tabEdit != null) removeKeyFromOrder(match.tabEdit.sectionsOrder, sectionKey);
|
|
1326
|
+
this.removeSectionVisibility(sectionKey);
|
|
1021
1327
|
}
|
|
1022
1328
|
for (const [sections, indexes] of byArray.entries()) {
|
|
1023
1329
|
removeIndexes(sections, indexes);
|
|
1024
1330
|
}
|
|
1025
|
-
this.removeSectionVisibility(sectionKey);
|
|
1026
1331
|
return this;
|
|
1027
1332
|
}
|
|
1028
1333
|
|
|
@@ -1141,9 +1446,10 @@ export class CustomObjectPageLayout {
|
|
|
1141
1446
|
field: Field,
|
|
1142
1447
|
fallbackSectionKey: string,
|
|
1143
1448
|
options: AddPageLayoutFieldOptions,
|
|
1449
|
+
fieldSection: CustomObjectFieldSection | undefined,
|
|
1144
1450
|
): SerializedSectionField {
|
|
1145
1451
|
this.assertFieldBelongsToLayout(field);
|
|
1146
|
-
this.assignFieldSectionForMutation(field, fallbackSectionKey,
|
|
1452
|
+
this.assignFieldSectionForMutation(field, fallbackSectionKey, fieldSection);
|
|
1147
1453
|
return toSerializedSectionField(field.getApiName(), options);
|
|
1148
1454
|
}
|
|
1149
1455
|
|
|
@@ -1171,23 +1477,22 @@ export class CustomObjectPageLayout {
|
|
|
1171
1477
|
}
|
|
1172
1478
|
|
|
1173
1479
|
private normalizeSectionForMutation(input: PageLayoutSection): SerializedFieldsSection {
|
|
1174
|
-
|
|
1175
|
-
throw new Error(
|
|
1176
|
-
`CustomObjectPageLayout section "${input.section.getSectionId()}" belongs to ` +
|
|
1177
|
-
`"${input.section.getModelApiName()}", but the layout belongs to "${this._customObjectApiName}".`,
|
|
1178
|
-
);
|
|
1179
|
-
}
|
|
1480
|
+
this.assertFieldSectionBelongsToLayout(input.section);
|
|
1180
1481
|
|
|
1181
|
-
const
|
|
1482
|
+
const sectionId = input.section.getSectionId();
|
|
1483
|
+
if (inputFieldsSectionHasKey(input)) {
|
|
1484
|
+
throw new Error('CustomObjectPageLayout fields_section key is not supported. Omit key.');
|
|
1485
|
+
}
|
|
1182
1486
|
const name = input.name ?? input.section.getName();
|
|
1183
1487
|
const normalized: SerializedFieldsSection = {
|
|
1184
|
-
key,
|
|
1185
1488
|
name,
|
|
1186
1489
|
type: 'fields_section',
|
|
1187
|
-
fields: input.fields.map((field) =>
|
|
1490
|
+
fields: input.fields.map((field) =>
|
|
1491
|
+
this.normalizeSectionFieldForMutation(input.section, sectionId, field),
|
|
1492
|
+
),
|
|
1188
1493
|
};
|
|
1189
1494
|
if (input.layout != null) normalized.layout = input.layout;
|
|
1190
|
-
return normalized;
|
|
1495
|
+
return attachFieldSectionId(normalized, sectionId);
|
|
1191
1496
|
}
|
|
1192
1497
|
|
|
1193
1498
|
private normalizeTabForMutation(tab: PageLayoutTab): SerializedPageLayoutTab {
|
|
@@ -1207,7 +1512,7 @@ export class CustomObjectPageLayout {
|
|
|
1207
1512
|
if (rawFieldSection != null) {
|
|
1208
1513
|
if (typeof rawFieldSection === 'string') {
|
|
1209
1514
|
throw new TypeError(
|
|
1210
|
-
`CustomObjectPageLayout
|
|
1515
|
+
`CustomObjectPageLayout section must be a CustomObjectFieldSection object; ` +
|
|
1211
1516
|
`received raw section id "${rawFieldSection}". Load/include the existing field section first.`,
|
|
1212
1517
|
);
|
|
1213
1518
|
}
|
|
@@ -1226,7 +1531,7 @@ export class CustomObjectPageLayout {
|
|
|
1226
1531
|
if (field.getSectionId() == null) {
|
|
1227
1532
|
throw new Error(
|
|
1228
1533
|
`CustomObjectPageLayout field "${field.getApiName()}" is missing field-section metadata. ` +
|
|
1229
|
-
`Construct the field with a CustomObjectFieldSection or pass
|
|
1534
|
+
`Construct the field with a CustomObjectFieldSection or pass section when adding it to ` +
|
|
1230
1535
|
`section "${fallbackSectionKey}".`,
|
|
1231
1536
|
);
|
|
1232
1537
|
}
|
|
@@ -1241,17 +1546,63 @@ export class CustomObjectPageLayout {
|
|
|
1241
1546
|
}
|
|
1242
1547
|
}
|
|
1243
1548
|
|
|
1244
|
-
private
|
|
1549
|
+
private assertFieldSectionBelongsToLayout(section: CustomObjectFieldSection): void {
|
|
1550
|
+
if (section.getModelApiName() !== this._customObjectApiName) {
|
|
1551
|
+
throw new Error(
|
|
1552
|
+
`CustomObjectPageLayout field section "${section.getSectionId()}" belongs to ` +
|
|
1553
|
+
`"${section.getModelApiName()}", but the layout belongs to "${this._customObjectApiName}".`,
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
private requireOneFieldContainer(
|
|
1559
|
+
tabKey: string | undefined,
|
|
1560
|
+
sectionKey: string | undefined,
|
|
1561
|
+
fallbackFieldSection?: CustomObjectFieldSection,
|
|
1562
|
+
): MutableFieldContainer {
|
|
1245
1563
|
const containers = this.findFieldContainers({
|
|
1246
1564
|
tabKey,
|
|
1247
1565
|
sectionKey,
|
|
1248
1566
|
systemSectionOnly: false,
|
|
1249
1567
|
});
|
|
1250
1568
|
if (containers.length === 0) {
|
|
1251
|
-
|
|
1252
|
-
|
|
1569
|
+
if (sectionKey != null && fallbackFieldSection != null) {
|
|
1570
|
+
const unkeyedContainers = this.findFieldContainers({
|
|
1571
|
+
tabKey,
|
|
1572
|
+
sectionKey: undefined,
|
|
1573
|
+
systemSectionOnly: false,
|
|
1574
|
+
}).filter((container) => container.section != null && container.sectionKey == null);
|
|
1575
|
+
if (unkeyedContainers.length === 1) {
|
|
1576
|
+
if (fallbackFieldSection.getModelApiName() !== this._customObjectApiName) {
|
|
1577
|
+
throw new Error(
|
|
1578
|
+
`CustomObjectPageLayout field section "${fallbackFieldSection.getSectionId()}" belongs to ` +
|
|
1579
|
+
`"${fallbackFieldSection.getModelApiName()}", but the layout belongs to "${
|
|
1580
|
+
this._customObjectApiName
|
|
1581
|
+
}".`,
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
const container = unkeyedContainers[0] as MutableFieldContainer & {
|
|
1585
|
+
section: SerializedFieldsSection;
|
|
1586
|
+
};
|
|
1587
|
+
attachFieldSectionId(container.section, fallbackFieldSection.getSectionId());
|
|
1588
|
+
container.sectionKey = fallbackFieldSection.getSectionId();
|
|
1589
|
+
container.label = `${container.tabKey}.${container.sectionKey}`;
|
|
1590
|
+
return container;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
const scope =
|
|
1594
|
+
sectionKey == null ? 'a field section' : (
|
|
1595
|
+
`section "${tabKey != null ? `${tabKey}.` : ''}${sectionKey}"`
|
|
1596
|
+
);
|
|
1597
|
+
throw new Error(`CustomObjectPageLayout could not find ${scope}.`);
|
|
1253
1598
|
}
|
|
1254
1599
|
if (containers.length > 1) {
|
|
1600
|
+
if (sectionKey == null) {
|
|
1601
|
+
throw new Error(
|
|
1602
|
+
`CustomObjectPageLayout matched multiple sections: ` +
|
|
1603
|
+
`${containers.map((container) => container.label).join(', ')}. Pass section to choose one.`,
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1255
1606
|
throw new Error(
|
|
1256
1607
|
`CustomObjectPageLayout section "${sectionKey}" matched multiple sections: ` +
|
|
1257
1608
|
`${containers.map((container) => container.label).join(', ')}. Pass tabKey to choose one.`,
|
|
@@ -1271,34 +1622,40 @@ export class CustomObjectPageLayout {
|
|
|
1271
1622
|
for (const tab of this._tabEdits.newTabs ?? []) {
|
|
1272
1623
|
if (!isTabWithSections(tab)) continue;
|
|
1273
1624
|
if (filter.tabKey != null && tab.key !== filter.tabKey) continue;
|
|
1274
|
-
for (const section of tab.sections) {
|
|
1275
|
-
|
|
1625
|
+
for (const [index, section] of tab.sections.entries()) {
|
|
1626
|
+
const sectionKey = layoutSectionKey(section);
|
|
1627
|
+
if (filter.sectionKey != null && !layoutSectionMatches(section, filter.sectionKey)) continue;
|
|
1628
|
+
const label = sectionKey == null ? `${tab.key}.sections[${index}]` : `${tab.key}.${sectionKey}`;
|
|
1276
1629
|
containers.push({
|
|
1277
1630
|
tabKey: tab.key,
|
|
1278
|
-
sectionKey
|
|
1631
|
+
sectionKey,
|
|
1632
|
+
section,
|
|
1279
1633
|
fields: ensureArrayProperty<MutableSectionField>(
|
|
1280
1634
|
section as unknown as Record<string, any>,
|
|
1281
1635
|
'fields',
|
|
1282
|
-
`newTabs.${
|
|
1636
|
+
`newTabs.${label}.fields`,
|
|
1283
1637
|
),
|
|
1284
|
-
label
|
|
1638
|
+
label,
|
|
1285
1639
|
});
|
|
1286
1640
|
}
|
|
1287
1641
|
}
|
|
1288
1642
|
|
|
1289
1643
|
for (const [tabKey, tabEdit] of Object.entries(this._tabEdits.systemTabEdits ?? {})) {
|
|
1290
1644
|
if (filter.tabKey != null && tabKey !== filter.tabKey) continue;
|
|
1291
|
-
for (const section of tabEdit.newSections ?? []) {
|
|
1292
|
-
|
|
1645
|
+
for (const [index, section] of (tabEdit.newSections ?? []).entries()) {
|
|
1646
|
+
const sectionKey = layoutSectionKey(section);
|
|
1647
|
+
if (filter.sectionKey != null && !layoutSectionMatches(section, filter.sectionKey)) continue;
|
|
1648
|
+
const label = sectionKey == null ? `${tabKey}.newSections[${index}]` : `${tabKey}.${sectionKey}`;
|
|
1293
1649
|
containers.push({
|
|
1294
1650
|
tabKey,
|
|
1295
|
-
sectionKey
|
|
1651
|
+
sectionKey,
|
|
1652
|
+
section,
|
|
1296
1653
|
fields: ensureArrayProperty<MutableSectionField>(
|
|
1297
1654
|
section as unknown as Record<string, any>,
|
|
1298
1655
|
'fields',
|
|
1299
|
-
`systemTabEdits.${
|
|
1656
|
+
`systemTabEdits.${label}.fields`,
|
|
1300
1657
|
),
|
|
1301
|
-
label
|
|
1658
|
+
label,
|
|
1302
1659
|
});
|
|
1303
1660
|
}
|
|
1304
1661
|
}
|
|
@@ -1329,11 +1686,14 @@ export class CustomObjectPageLayout {
|
|
|
1329
1686
|
|
|
1330
1687
|
private ensureSystemSectionFieldContainer(
|
|
1331
1688
|
tabKey: string | undefined,
|
|
1332
|
-
sectionKey: string,
|
|
1689
|
+
sectionKey: string | undefined,
|
|
1333
1690
|
): MutableFieldContainer {
|
|
1334
1691
|
if (tabKey == null) {
|
|
1335
1692
|
throw new Error('CustomObjectPageLayout addField with systemSection: true requires tabKey.');
|
|
1336
1693
|
}
|
|
1694
|
+
if (sectionKey == null) {
|
|
1695
|
+
throw new Error('CustomObjectPageLayout addField with systemSection: true requires sectionKey.');
|
|
1696
|
+
}
|
|
1337
1697
|
const sectionEdit = this.ensureSystemSectionEdit(tabKey, sectionKey);
|
|
1338
1698
|
if (sectionEdit.deleted === true) {
|
|
1339
1699
|
throw new Error(`CustomObjectPageLayout system section "${tabKey}.${sectionKey}" is marked deleted.`);
|
|
@@ -1396,12 +1756,12 @@ export class CustomObjectPageLayout {
|
|
|
1396
1756
|
|
|
1397
1757
|
private assertSectionKeyAvailable(
|
|
1398
1758
|
sections: SerializedFieldsSection[],
|
|
1399
|
-
sectionKey: string,
|
|
1759
|
+
sectionKey: string | undefined,
|
|
1400
1760
|
tabKey: string,
|
|
1401
1761
|
allowDuplicate: boolean | undefined,
|
|
1402
1762
|
): void {
|
|
1403
1763
|
if (allowDuplicate === true) return;
|
|
1404
|
-
if (sections.some((section) => section
|
|
1764
|
+
if (sectionKey != null && sections.some((section) => layoutSectionMatches(section, sectionKey))) {
|
|
1405
1765
|
throw new Error(`CustomObjectPageLayout tab "${tabKey}" already contains section "${sectionKey}".`);
|
|
1406
1766
|
}
|
|
1407
1767
|
}
|
|
@@ -1476,12 +1836,13 @@ export class CustomObjectPageLayout {
|
|
|
1476
1836
|
* tabs or sections. Always uses `apiName: 'default'`.
|
|
1477
1837
|
*
|
|
1478
1838
|
* @param customObject - The custom object to create the layout for.
|
|
1479
|
-
* @param props - Optional. Pass `fields` to populate the General section.
|
|
1839
|
+
* @param props - Optional. Pass `section` and `fields` to populate the General section.
|
|
1480
1840
|
* @returns A new `CustomObjectPageLayout` registered with the manifest.
|
|
1481
1841
|
*
|
|
1482
1842
|
* @example
|
|
1483
1843
|
* ```ts
|
|
1484
1844
|
* CustomObjectPageLayout.basic(memberObj, {
|
|
1845
|
+
* section: profileSection,
|
|
1485
1846
|
* fields: [memberFirstName, memberEmail, memberStatusField],
|
|
1486
1847
|
* });
|
|
1487
1848
|
* ```
|
|
@@ -1489,16 +1850,40 @@ export class CustomObjectPageLayout {
|
|
|
1489
1850
|
static basic(
|
|
1490
1851
|
customObject: CustomObject,
|
|
1491
1852
|
props: {
|
|
1853
|
+
section?: CustomObjectFieldSection;
|
|
1492
1854
|
fields?: Field[];
|
|
1493
1855
|
} = {},
|
|
1494
1856
|
): CustomObjectPageLayout {
|
|
1495
1857
|
const coApiName = customObject.getApiName();
|
|
1496
1858
|
const coName = customObject.getName();
|
|
1497
|
-
const
|
|
1498
|
-
const generalSection =
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1859
|
+
const fields = props.fields ?? [];
|
|
1860
|
+
const generalSection =
|
|
1861
|
+
props.section ??
|
|
1862
|
+
(() => {
|
|
1863
|
+
const alreadySectionedField = fields.find((field) => field.getSectionId() != null);
|
|
1864
|
+
if (alreadySectionedField != null) {
|
|
1865
|
+
throw new Error(
|
|
1866
|
+
`CustomObjectPageLayout.basic field "${alreadySectionedField.getApiName()}" already belongs to ` +
|
|
1867
|
+
`field section "${alreadySectionedField.getSectionId()}". Pass that CustomObjectFieldSection ` +
|
|
1868
|
+
'as section so basic() does not reassign the field.',
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
const sectionPrefix = coApiName.replace(/__c$/, '').replace(/[^a-zA-Z0-9]+/g, '_');
|
|
1872
|
+
return new CustomObjectFieldSection(customObject, {
|
|
1873
|
+
sectionId: `sec_${sectionPrefix}_general`,
|
|
1874
|
+
name: 'General',
|
|
1875
|
+
});
|
|
1876
|
+
})();
|
|
1877
|
+
assertSectionBelongsTo(customObject, generalSection);
|
|
1878
|
+
for (const field of fields) {
|
|
1879
|
+
const fieldSectionId = field.getSectionId();
|
|
1880
|
+
if (fieldSectionId != null && fieldSectionId !== generalSection.getSectionId()) {
|
|
1881
|
+
throw new Error(
|
|
1882
|
+
`CustomObjectPageLayout.basic field "${field.getApiName()}" belongs to field section ` +
|
|
1883
|
+
`"${fieldSectionId}", but basic() was given section "${generalSection.getSectionId()}".`,
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1502
1887
|
return new CustomObjectPageLayout(customObject, {
|
|
1503
1888
|
apiName: 'default',
|
|
1504
1889
|
name: `${coName} Layout`,
|
|
@@ -1510,11 +1895,10 @@ export class CustomObjectPageLayout {
|
|
|
1510
1895
|
type: 'tab_with_sections',
|
|
1511
1896
|
sections: [
|
|
1512
1897
|
{
|
|
1513
|
-
key: 'general',
|
|
1514
1898
|
name: 'General',
|
|
1515
1899
|
type: 'fields_section',
|
|
1516
1900
|
section: generalSection,
|
|
1517
|
-
fields
|
|
1901
|
+
fields,
|
|
1518
1902
|
},
|
|
1519
1903
|
],
|
|
1520
1904
|
},
|