@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
|
@@ -14,6 +14,35 @@ function isFieldRef(value) {
|
|
|
14
14
|
const candidate = value;
|
|
15
15
|
return candidate != null && typeof candidate === 'object' && isField(candidate.field);
|
|
16
16
|
}
|
|
17
|
+
const FIELD_SECTION_ID = Symbol('CustomObjectPageLayout.fieldSectionId');
|
|
18
|
+
function isCustomObjectFieldSection(value) {
|
|
19
|
+
return value instanceof CustomObjectFieldSection;
|
|
20
|
+
}
|
|
21
|
+
function attachFieldSectionId(section, sectionId) {
|
|
22
|
+
Object.defineProperty(section, FIELD_SECTION_ID, {
|
|
23
|
+
value: sectionId,
|
|
24
|
+
enumerable: false,
|
|
25
|
+
configurable: true,
|
|
26
|
+
});
|
|
27
|
+
return section;
|
|
28
|
+
}
|
|
29
|
+
function layoutSectionKey(section) {
|
|
30
|
+
return section[FIELD_SECTION_ID];
|
|
31
|
+
}
|
|
32
|
+
function layoutSectionMatches(section, sectionKey) {
|
|
33
|
+
return layoutSectionKey(section) === sectionKey;
|
|
34
|
+
}
|
|
35
|
+
function inputFieldsSectionHasKey(input) {
|
|
36
|
+
const record = input;
|
|
37
|
+
return Object.prototype.hasOwnProperty.call(record, 'key') && record['key'] != null;
|
|
38
|
+
}
|
|
39
|
+
function assertNoSerializedSectionKey(section) {
|
|
40
|
+
const record = section;
|
|
41
|
+
if (Object.prototype.hasOwnProperty.call(record, 'key') && record['key'] != null) {
|
|
42
|
+
throw new Error('CustomObjectPageLayout loaded fields_section key is not supported. ' +
|
|
43
|
+
'Remove key and target the section with CustomObjectFieldSection metadata.');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
17
46
|
function assertSameObject(customObject, field, sectionKey) {
|
|
18
47
|
if (field.getCustomObjectApiName() !== customObject.getApiName()) {
|
|
19
48
|
throw new Error(`CustomObjectPageLayout fields_section "${sectionKey}" references field "${field.getApiName()}" ` +
|
|
@@ -26,6 +55,21 @@ function assertSectionBelongsTo(customObject, section) {
|
|
|
26
55
|
`but the layout belongs to "${customObject.getApiName()}".`);
|
|
27
56
|
}
|
|
28
57
|
}
|
|
58
|
+
function requireFieldSectionOption(value, label) {
|
|
59
|
+
if (isCustomObjectFieldSection(value))
|
|
60
|
+
return value;
|
|
61
|
+
if (typeof value === 'string') {
|
|
62
|
+
throw new TypeError(`${label} must be a CustomObjectFieldSection object; received raw section id "${value}". ` +
|
|
63
|
+
'Load/include the existing field section first.');
|
|
64
|
+
}
|
|
65
|
+
throw new TypeError(`${label} must be a CustomObjectFieldSection object.`);
|
|
66
|
+
}
|
|
67
|
+
function assertOptionAbsent(options, key, message) {
|
|
68
|
+
if (options == null || typeof options !== 'object')
|
|
69
|
+
return;
|
|
70
|
+
if (Object.prototype.hasOwnProperty.call(options, key))
|
|
71
|
+
throw new TypeError(message);
|
|
72
|
+
}
|
|
29
73
|
function toSerializedSectionField(fieldRqlName, options = {}) {
|
|
30
74
|
const normalized = { fieldRqlName };
|
|
31
75
|
if (options.hidden != null)
|
|
@@ -52,17 +96,19 @@ function normalizeSectionField(customObject, section, sectionKey, entry) {
|
|
|
52
96
|
}
|
|
53
97
|
function normalizeFieldsSection(customObject, input) {
|
|
54
98
|
assertSectionBelongsTo(customObject, input.section);
|
|
55
|
-
const
|
|
99
|
+
const sectionId = input.section.getSectionId();
|
|
100
|
+
if (inputFieldsSectionHasKey(input)) {
|
|
101
|
+
throw new Error('CustomObjectPageLayout fields_section key is not supported. Omit key.');
|
|
102
|
+
}
|
|
56
103
|
const name = input.name ?? input.section.getName();
|
|
57
104
|
const normalized = {
|
|
58
|
-
key,
|
|
59
105
|
name,
|
|
60
106
|
type: 'fields_section',
|
|
61
|
-
fields: input.fields.map((field) => normalizeSectionField(customObject, input.section,
|
|
107
|
+
fields: input.fields.map((field) => normalizeSectionField(customObject, input.section, sectionId, field)),
|
|
62
108
|
};
|
|
63
109
|
if (input.layout != null)
|
|
64
110
|
normalized.layout = input.layout;
|
|
65
|
-
return normalized;
|
|
111
|
+
return attachFieldSectionId(normalized, sectionId);
|
|
66
112
|
}
|
|
67
113
|
function normalizeTab(customObject, tab) {
|
|
68
114
|
if (tab.type === 'custom_tab')
|
|
@@ -115,6 +161,35 @@ function cloneJson(value) {
|
|
|
115
161
|
return value;
|
|
116
162
|
return JSON.parse(JSON.stringify(value));
|
|
117
163
|
}
|
|
164
|
+
const HEADER_EDIT_KEYS = new Set([
|
|
165
|
+
'newTitleField',
|
|
166
|
+
'titleFieldDeleted',
|
|
167
|
+
'newDescriptionField',
|
|
168
|
+
'descriptionFieldDeleted',
|
|
169
|
+
'systemFieldEdits',
|
|
170
|
+
'newFields',
|
|
171
|
+
'headerFieldsOrder',
|
|
172
|
+
'buttons',
|
|
173
|
+
]);
|
|
174
|
+
const SYSTEM_HEADER_FIELD_EDIT_KEYS = new Set(['newRqlField', 'deleted']);
|
|
175
|
+
const NEW_HEADER_FIELD_KEYS = new Set(['rqlName', 'key', 'canBeDeleted', 'canBeChanged', 'canBeMoved']);
|
|
176
|
+
function assertKnownKeys(record, allowedKeys, label, hint) {
|
|
177
|
+
for (const key of Object.keys(record)) {
|
|
178
|
+
if (!allowedKeys.has(key)) {
|
|
179
|
+
throw new Error(`${label} contains unsupported key "${key}". ${hint}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function normalizeHeaderEdits(headerEdits) {
|
|
184
|
+
assertKnownKeys(headerEdits, HEADER_EDIT_KEYS, 'CustomObjectPageLayout headerEdits', 'Use addHeaderField(), setTitleField(), setDescriptionField(), or the HeaderEdits keys documented by the SDK.');
|
|
185
|
+
for (const [key, edit] of Object.entries(headerEdits.systemFieldEdits ?? {})) {
|
|
186
|
+
assertKnownKeys(edit, SYSTEM_HEADER_FIELD_EDIT_KEYS, `CustomObjectPageLayout headerEdits.systemFieldEdits.${key}`, 'Use newRqlField and deleted for system header field edits.');
|
|
187
|
+
}
|
|
188
|
+
for (const [index, field] of (headerEdits.newFields ?? []).entries()) {
|
|
189
|
+
assertKnownKeys(field, NEW_HEADER_FIELD_KEYS, `CustomObjectPageLayout headerEdits.newFields[${index}]`, 'Use { rqlName, key } entries, or call addHeaderField(field).');
|
|
190
|
+
}
|
|
191
|
+
return headerEdits;
|
|
192
|
+
}
|
|
118
193
|
function isTabWithSections(tab) {
|
|
119
194
|
return tab.type === 'tab_with_sections';
|
|
120
195
|
}
|
|
@@ -140,6 +215,48 @@ function fieldRqlNameOf(entry) {
|
|
|
140
215
|
const fieldRqlName = record['fieldRqlName'] ?? record['field_rql_name'];
|
|
141
216
|
return typeof fieldRqlName === 'string' ? fieldRqlName : undefined;
|
|
142
217
|
}
|
|
218
|
+
function explicitFieldSectionId(entry) {
|
|
219
|
+
if (entry == null || typeof entry !== 'object')
|
|
220
|
+
return undefined;
|
|
221
|
+
const record = entry;
|
|
222
|
+
const fieldSectionId = record['fieldSection'] ?? record['field_section'];
|
|
223
|
+
return typeof fieldSectionId === 'string' ? fieldSectionId : undefined;
|
|
224
|
+
}
|
|
225
|
+
function inferredSectionIdFromFields(section, resolveFieldSectionId) {
|
|
226
|
+
const sectionIds = new Set();
|
|
227
|
+
for (const field of section.fields ?? []) {
|
|
228
|
+
const explicitSectionId = explicitFieldSectionId(field);
|
|
229
|
+
if (explicitSectionId != null) {
|
|
230
|
+
sectionIds.add(explicitSectionId);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const fieldApiName = fieldRqlNameOf(field);
|
|
234
|
+
if (fieldApiName == null || resolveFieldSectionId == null)
|
|
235
|
+
continue;
|
|
236
|
+
const resolvedSectionId = resolveFieldSectionId(fieldApiName);
|
|
237
|
+
if (resolvedSectionId != null)
|
|
238
|
+
sectionIds.add(resolvedSectionId);
|
|
239
|
+
}
|
|
240
|
+
return sectionIds.size === 1 ? [...sectionIds][0] : undefined;
|
|
241
|
+
}
|
|
242
|
+
function annotateLoadedFieldsSection(section, resolveFieldSectionId) {
|
|
243
|
+
assertNoSerializedSectionKey(section);
|
|
244
|
+
const sectionId = layoutSectionKey(section) ?? inferredSectionIdFromFields(section, resolveFieldSectionId);
|
|
245
|
+
return sectionId == null ? section : attachFieldSectionId(section, sectionId);
|
|
246
|
+
}
|
|
247
|
+
function annotateLoadedTabEdits(tabEdits, resolveFieldSectionId) {
|
|
248
|
+
for (const tab of tabEdits.newTabs ?? []) {
|
|
249
|
+
if (!isTabWithSections(tab))
|
|
250
|
+
continue;
|
|
251
|
+
tab.sections = tab.sections.map((section) => annotateLoadedFieldsSection(section, resolveFieldSectionId));
|
|
252
|
+
}
|
|
253
|
+
for (const tabEdit of Object.values(tabEdits.systemTabEdits ?? {})) {
|
|
254
|
+
if (tabEdit.newSections != null) {
|
|
255
|
+
tabEdit.newSections = tabEdit.newSections.map((section) => annotateLoadedFieldsSection(section, resolveFieldSectionId));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return tabEdits;
|
|
259
|
+
}
|
|
143
260
|
function removeIndexes(items, indexes) {
|
|
144
261
|
for (const index of [...indexes].sort((a, b) => b - a)) {
|
|
145
262
|
items.splice(index, 1);
|
|
@@ -213,7 +330,6 @@ function ensureArrayProperty(record, key, label) {
|
|
|
213
330
|
* newTabs: [{
|
|
214
331
|
* key: 'member', name: 'Member', type: 'tab_with_sections',
|
|
215
332
|
* sections: [{
|
|
216
|
-
* key: 'profile',
|
|
217
333
|
* name: 'Profile',
|
|
218
334
|
* type: 'fields_section',
|
|
219
335
|
* section: profileSection,
|
|
@@ -252,7 +368,7 @@ export class CustomObjectPageLayout {
|
|
|
252
368
|
this._customObjectApiName = customObject.getApiName();
|
|
253
369
|
this._name = props.name;
|
|
254
370
|
this._tabEdits = normalizeTabEdits(customObject, props.tabEdits ?? {});
|
|
255
|
-
this._headerEdits = props.headerEdits ?? {};
|
|
371
|
+
this._headerEdits = normalizeHeaderEdits(props.headerEdits ?? {});
|
|
256
372
|
this._visibilityConditions = props.visibilityConditions ?? {};
|
|
257
373
|
this._blueprintKey = props.blueprintKey;
|
|
258
374
|
customObject._register(this);
|
|
@@ -268,14 +384,14 @@ export class CustomObjectPageLayout {
|
|
|
268
384
|
return getExistingManifestContext(options).loadPageLayout(layoutIdOrApiName, options);
|
|
269
385
|
}
|
|
270
386
|
/** @internal Hydrates a page layout from existing manifest wire JSON. */
|
|
271
|
-
static _fromExistingComponent(customObject, component) {
|
|
387
|
+
static _fromExistingComponent(customObject, component, resolveFieldSectionId) {
|
|
272
388
|
const layout = Object.create(CustomObjectPageLayout.prototype);
|
|
273
389
|
Object.assign(layout, {
|
|
274
390
|
_layoutId: requireStringComponentField(component, 'layout_id', CustomObjectPageLayout.componentType),
|
|
275
391
|
_apiName: requireStringComponentField(component, 'api_name', CustomObjectPageLayout.componentType),
|
|
276
392
|
_customObjectApiName: requireStringComponentField(component, 'object_rql_name', CustomObjectPageLayout.componentType),
|
|
277
393
|
_name: requireStringComponentField(component, 'name', CustomObjectPageLayout.componentType),
|
|
278
|
-
_tabEdits: cloneJson(component['tab_edits'] ?? {}),
|
|
394
|
+
_tabEdits: annotateLoadedTabEdits(cloneJson(component['tab_edits'] ?? {}), resolveFieldSectionId),
|
|
279
395
|
_headerEdits: cloneJson(component['header_edits'] ?? {}),
|
|
280
396
|
_visibilityConditions: cloneJson(component['visibility_conditions'] ?? {}),
|
|
281
397
|
_blueprintKey: component['blueprint_key'],
|
|
@@ -313,17 +429,32 @@ export class CustomObjectPageLayout {
|
|
|
313
429
|
/**
|
|
314
430
|
* Adds multiple fields to an existing layout section.
|
|
315
431
|
*
|
|
316
|
-
* Fields are inserted in the order provided.
|
|
317
|
-
*
|
|
318
|
-
* `CUSTOM_OBJECT_FIELD_SECTION
|
|
432
|
+
* Fields are inserted in the order provided. For custom layout sections,
|
|
433
|
+
* pass `options.section` so TypeScript and runtime validation agree on the
|
|
434
|
+
* target `CUSTOM_OBJECT_FIELD_SECTION`.
|
|
319
435
|
*/
|
|
320
436
|
addFields(fields, options) {
|
|
321
437
|
if (fields.length === 0)
|
|
322
438
|
return this;
|
|
439
|
+
let fieldSectionForMutation;
|
|
323
440
|
const container = options.systemSection === true ?
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
441
|
+
(() => {
|
|
442
|
+
assertOptionAbsent(options, 'section', 'CustomObjectPageLayout addField section is only supported for custom layout sections.');
|
|
443
|
+
assertOptionAbsent(options, 'fieldSection', 'CustomObjectPageLayout addField fieldSection is no longer supported. ' +
|
|
444
|
+
'Use sectionKey only for systemSection: true.');
|
|
445
|
+
return this.ensureSystemSectionFieldContainer(options.tabKey, options.sectionKey);
|
|
446
|
+
})()
|
|
447
|
+
: (() => {
|
|
448
|
+
assertOptionAbsent(options, 'sectionKey', 'CustomObjectPageLayout addField sectionKey is only supported with systemSection: true. ' +
|
|
449
|
+
'Pass section: CustomObjectFieldSection for custom layout sections.');
|
|
450
|
+
assertOptionAbsent(options, 'fieldSection', 'CustomObjectPageLayout addField fieldSection is no longer supported. ' +
|
|
451
|
+
'Pass section: CustomObjectFieldSection instead.');
|
|
452
|
+
const section = requireFieldSectionOption(options.section, 'CustomObjectPageLayout addField section');
|
|
453
|
+
this.assertFieldSectionBelongsToLayout(section);
|
|
454
|
+
fieldSectionForMutation = section;
|
|
455
|
+
return this.requireOneFieldContainer(options.tabKey, section.getSectionId(), section);
|
|
456
|
+
})();
|
|
457
|
+
const entries = fields.map((field) => this.serializeFieldForMutation(field, container.sectionKey ?? container.label, options, fieldSectionForMutation));
|
|
327
458
|
if (options.allowDuplicate !== true) {
|
|
328
459
|
const existing = new Set(container.fields.map((entry) => fieldRqlNameOf(entry)).filter(Boolean));
|
|
329
460
|
const seen = new Set();
|
|
@@ -349,9 +480,23 @@ export class CustomObjectPageLayout {
|
|
|
349
480
|
const fieldApiName = typeof field === 'string' ? field : field.getApiName();
|
|
350
481
|
if (isField(field))
|
|
351
482
|
this.assertFieldBelongsToLayout(field);
|
|
483
|
+
let sectionKey;
|
|
484
|
+
if (options.systemSection === true) {
|
|
485
|
+
assertOptionAbsent(options, 'section', 'CustomObjectPageLayout removeField section is only supported for custom layout sections.');
|
|
486
|
+
sectionKey = options.sectionKey;
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
assertOptionAbsent(options, 'sectionKey', 'CustomObjectPageLayout removeField sectionKey is only supported with systemSection: true. ' +
|
|
490
|
+
'Pass section: CustomObjectFieldSection for custom layout sections.');
|
|
491
|
+
if (options.section != null) {
|
|
492
|
+
const section = requireFieldSectionOption(options.section, 'CustomObjectPageLayout removeField section');
|
|
493
|
+
this.assertFieldSectionBelongsToLayout(section);
|
|
494
|
+
sectionKey = section.getSectionId();
|
|
495
|
+
}
|
|
496
|
+
}
|
|
352
497
|
const containers = this.findFieldContainers({
|
|
353
498
|
tabKey: options.tabKey,
|
|
354
|
-
sectionKey
|
|
499
|
+
sectionKey,
|
|
355
500
|
systemSectionOnly: options.systemSection === true,
|
|
356
501
|
});
|
|
357
502
|
const matches = [];
|
|
@@ -400,10 +545,8 @@ export class CustomObjectPageLayout {
|
|
|
400
545
|
if (options.systemTab === true) {
|
|
401
546
|
const tabEdit = this.ensureSystemTabEdit(options.tabKey);
|
|
402
547
|
const sections = ensureArrayProperty(tabEdit, 'newSections', `systemTabEdits.${options.tabKey}.newSections`);
|
|
403
|
-
this.assertSectionKeyAvailable(sections, normalized
|
|
548
|
+
this.assertSectionKeyAvailable(sections, layoutSectionKey(normalized), options.tabKey, options.allowDuplicate);
|
|
404
549
|
insertAt(sections, normalized, options.position);
|
|
405
|
-
if (tabEdit.sectionsOrder != null)
|
|
406
|
-
insertAt(tabEdit.sectionsOrder, normalized.key, options.position);
|
|
407
550
|
return this;
|
|
408
551
|
}
|
|
409
552
|
const tab = this.findNewTabWithSections(options.tabKey);
|
|
@@ -411,26 +554,26 @@ export class CustomObjectPageLayout {
|
|
|
411
554
|
throw new Error(`CustomObjectPageLayout could not find new tab "${options.tabKey}". ` +
|
|
412
555
|
'Pass systemTab: true to add a section to a built-in system tab.');
|
|
413
556
|
}
|
|
414
|
-
this.assertSectionKeyAvailable(tab.sections, normalized
|
|
557
|
+
this.assertSectionKeyAvailable(tab.sections, layoutSectionKey(normalized), options.tabKey, options.allowDuplicate);
|
|
415
558
|
insertAt(tab.sections, normalized, options.position);
|
|
416
559
|
return this;
|
|
417
560
|
}
|
|
418
|
-
|
|
419
|
-
* Removes a section from `newTabs` / `systemTabEdits.newSections`.
|
|
420
|
-
*
|
|
421
|
-
* Pass `systemSection: true` with `tabKey` to delete a built-in section via
|
|
422
|
-
* `systemSectionEdits[sectionKey].deleted`.
|
|
423
|
-
*/
|
|
424
|
-
removeSection(sectionKey, options = {}) {
|
|
561
|
+
removeSection(sectionOrKey, options = {}) {
|
|
425
562
|
if (options.systemSection === true) {
|
|
563
|
+
if (typeof sectionOrKey !== 'string') {
|
|
564
|
+
throw new TypeError('CustomObjectPageLayout removeSection with systemSection: true requires a system section key.');
|
|
565
|
+
}
|
|
426
566
|
if (options.tabKey == null) {
|
|
427
567
|
throw new Error('CustomObjectPageLayout removeSection with systemSection: true requires tabKey.');
|
|
428
568
|
}
|
|
429
|
-
const sectionEdit = this.ensureSystemSectionEdit(options.tabKey,
|
|
569
|
+
const sectionEdit = this.ensureSystemSectionEdit(options.tabKey, sectionOrKey);
|
|
430
570
|
sectionEdit.deleted = true;
|
|
431
|
-
this.removeSectionVisibility(
|
|
571
|
+
this.removeSectionVisibility(sectionOrKey);
|
|
432
572
|
return this;
|
|
433
573
|
}
|
|
574
|
+
const section = requireFieldSectionOption(sectionOrKey, 'CustomObjectPageLayout removeSection section');
|
|
575
|
+
this.assertFieldSectionBelongsToLayout(section);
|
|
576
|
+
const sectionKey = section.getSectionId();
|
|
434
577
|
const matches = [];
|
|
435
578
|
for (const tab of this._tabEdits.newTabs ?? []) {
|
|
436
579
|
if (!isTabWithSections(tab))
|
|
@@ -438,16 +581,16 @@ export class CustomObjectPageLayout {
|
|
|
438
581
|
if (options.tabKey != null && tab.key !== options.tabKey)
|
|
439
582
|
continue;
|
|
440
583
|
tab.sections.forEach((section, index) => {
|
|
441
|
-
if (section
|
|
442
|
-
matches.push({ sections: tab.sections, index, tabKey: tab.key });
|
|
584
|
+
if (layoutSectionMatches(section, sectionKey))
|
|
585
|
+
matches.push({ sections: tab.sections, section, index, tabKey: tab.key });
|
|
443
586
|
});
|
|
444
587
|
}
|
|
445
588
|
for (const [tabKey, tabEdit] of Object.entries(this._tabEdits.systemTabEdits ?? {})) {
|
|
446
589
|
if (options.tabKey != null && tabKey !== options.tabKey)
|
|
447
590
|
continue;
|
|
448
591
|
(tabEdit.newSections ?? []).forEach((section, index) => {
|
|
449
|
-
if (section
|
|
450
|
-
matches.push({ sections: tabEdit.newSections, index, tabEdit, tabKey });
|
|
592
|
+
if (layoutSectionMatches(section, sectionKey))
|
|
593
|
+
matches.push({ sections: tabEdit.newSections, section, index, tabEdit, tabKey });
|
|
451
594
|
});
|
|
452
595
|
}
|
|
453
596
|
if (matches.length === 0) {
|
|
@@ -466,11 +609,11 @@ export class CustomObjectPageLayout {
|
|
|
466
609
|
byArray.set(match.sections, indexes);
|
|
467
610
|
if (match.tabEdit != null)
|
|
468
611
|
removeKeyFromOrder(match.tabEdit.sectionsOrder, sectionKey);
|
|
612
|
+
this.removeSectionVisibility(sectionKey);
|
|
469
613
|
}
|
|
470
614
|
for (const [sections, indexes] of byArray.entries()) {
|
|
471
615
|
removeIndexes(sections, indexes);
|
|
472
616
|
}
|
|
473
|
-
this.removeSectionVisibility(sectionKey);
|
|
474
617
|
return this;
|
|
475
618
|
}
|
|
476
619
|
/** Adds a new tab to `tab_edits.newTabs`. */
|
|
@@ -571,9 +714,9 @@ export class CustomObjectPageLayout {
|
|
|
571
714
|
this._headerEdits.descriptionFieldDeleted = true;
|
|
572
715
|
return this;
|
|
573
716
|
}
|
|
574
|
-
serializeFieldForMutation(field, fallbackSectionKey, options) {
|
|
717
|
+
serializeFieldForMutation(field, fallbackSectionKey, options, fieldSection) {
|
|
575
718
|
this.assertFieldBelongsToLayout(field);
|
|
576
|
-
this.assignFieldSectionForMutation(field, fallbackSectionKey,
|
|
719
|
+
this.assignFieldSectionForMutation(field, fallbackSectionKey, fieldSection);
|
|
577
720
|
return toSerializedSectionField(field.getApiName(), options);
|
|
578
721
|
}
|
|
579
722
|
normalizeSectionFieldForMutation(section, sectionKey, entry) {
|
|
@@ -591,21 +734,20 @@ export class CustomObjectPageLayout {
|
|
|
591
734
|
'pass a Field instance or { field }.');
|
|
592
735
|
}
|
|
593
736
|
normalizeSectionForMutation(input) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
737
|
+
this.assertFieldSectionBelongsToLayout(input.section);
|
|
738
|
+
const sectionId = input.section.getSectionId();
|
|
739
|
+
if (inputFieldsSectionHasKey(input)) {
|
|
740
|
+
throw new Error('CustomObjectPageLayout fields_section key is not supported. Omit key.');
|
|
597
741
|
}
|
|
598
|
-
const key = input.key ?? input.section.getSectionId();
|
|
599
742
|
const name = input.name ?? input.section.getName();
|
|
600
743
|
const normalized = {
|
|
601
|
-
key,
|
|
602
744
|
name,
|
|
603
745
|
type: 'fields_section',
|
|
604
|
-
fields: input.fields.map((field) => this.normalizeSectionFieldForMutation(input.section,
|
|
746
|
+
fields: input.fields.map((field) => this.normalizeSectionFieldForMutation(input.section, sectionId, field)),
|
|
605
747
|
};
|
|
606
748
|
if (input.layout != null)
|
|
607
749
|
normalized.layout = input.layout;
|
|
608
|
-
return normalized;
|
|
750
|
+
return attachFieldSectionId(normalized, sectionId);
|
|
609
751
|
}
|
|
610
752
|
normalizeTabForMutation(tab) {
|
|
611
753
|
if (tab.type === 'custom_tab')
|
|
@@ -619,7 +761,7 @@ export class CustomObjectPageLayout {
|
|
|
619
761
|
const rawFieldSection = fieldSection;
|
|
620
762
|
if (rawFieldSection != null) {
|
|
621
763
|
if (typeof rawFieldSection === 'string') {
|
|
622
|
-
throw new TypeError(`CustomObjectPageLayout
|
|
764
|
+
throw new TypeError(`CustomObjectPageLayout section must be a CustomObjectFieldSection object; ` +
|
|
623
765
|
`received raw section id "${rawFieldSection}". Load/include the existing field section first.`);
|
|
624
766
|
}
|
|
625
767
|
if (rawFieldSection.getModelApiName() !== this._customObjectApiName) {
|
|
@@ -631,7 +773,7 @@ export class CustomObjectPageLayout {
|
|
|
631
773
|
}
|
|
632
774
|
if (field.getSectionId() == null) {
|
|
633
775
|
throw new Error(`CustomObjectPageLayout field "${field.getApiName()}" is missing field-section metadata. ` +
|
|
634
|
-
`Construct the field with a CustomObjectFieldSection or pass
|
|
776
|
+
`Construct the field with a CustomObjectFieldSection or pass section when adding it to ` +
|
|
635
777
|
`section "${fallbackSectionKey}".`);
|
|
636
778
|
}
|
|
637
779
|
}
|
|
@@ -641,17 +783,45 @@ export class CustomObjectPageLayout {
|
|
|
641
783
|
`"${field.getCustomObjectApiName()}", but the layout belongs to "${this._customObjectApiName}".`);
|
|
642
784
|
}
|
|
643
785
|
}
|
|
644
|
-
|
|
786
|
+
assertFieldSectionBelongsToLayout(section) {
|
|
787
|
+
if (section.getModelApiName() !== this._customObjectApiName) {
|
|
788
|
+
throw new Error(`CustomObjectPageLayout field section "${section.getSectionId()}" belongs to ` +
|
|
789
|
+
`"${section.getModelApiName()}", but the layout belongs to "${this._customObjectApiName}".`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
requireOneFieldContainer(tabKey, sectionKey, fallbackFieldSection) {
|
|
645
793
|
const containers = this.findFieldContainers({
|
|
646
794
|
tabKey,
|
|
647
795
|
sectionKey,
|
|
648
796
|
systemSectionOnly: false,
|
|
649
797
|
});
|
|
650
798
|
if (containers.length === 0) {
|
|
651
|
-
|
|
652
|
-
|
|
799
|
+
if (sectionKey != null && fallbackFieldSection != null) {
|
|
800
|
+
const unkeyedContainers = this.findFieldContainers({
|
|
801
|
+
tabKey,
|
|
802
|
+
sectionKey: undefined,
|
|
803
|
+
systemSectionOnly: false,
|
|
804
|
+
}).filter((container) => container.section != null && container.sectionKey == null);
|
|
805
|
+
if (unkeyedContainers.length === 1) {
|
|
806
|
+
if (fallbackFieldSection.getModelApiName() !== this._customObjectApiName) {
|
|
807
|
+
throw new Error(`CustomObjectPageLayout field section "${fallbackFieldSection.getSectionId()}" belongs to ` +
|
|
808
|
+
`"${fallbackFieldSection.getModelApiName()}", but the layout belongs to "${this._customObjectApiName}".`);
|
|
809
|
+
}
|
|
810
|
+
const container = unkeyedContainers[0];
|
|
811
|
+
attachFieldSectionId(container.section, fallbackFieldSection.getSectionId());
|
|
812
|
+
container.sectionKey = fallbackFieldSection.getSectionId();
|
|
813
|
+
container.label = `${container.tabKey}.${container.sectionKey}`;
|
|
814
|
+
return container;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
const scope = sectionKey == null ? 'a field section' : (`section "${tabKey != null ? `${tabKey}.` : ''}${sectionKey}"`);
|
|
818
|
+
throw new Error(`CustomObjectPageLayout could not find ${scope}.`);
|
|
653
819
|
}
|
|
654
820
|
if (containers.length > 1) {
|
|
821
|
+
if (sectionKey == null) {
|
|
822
|
+
throw new Error(`CustomObjectPageLayout matched multiple sections: ` +
|
|
823
|
+
`${containers.map((container) => container.label).join(', ')}. Pass section to choose one.`);
|
|
824
|
+
}
|
|
655
825
|
throw new Error(`CustomObjectPageLayout section "${sectionKey}" matched multiple sections: ` +
|
|
656
826
|
`${containers.map((container) => container.label).join(', ')}. Pass tabKey to choose one.`);
|
|
657
827
|
}
|
|
@@ -665,28 +835,34 @@ export class CustomObjectPageLayout {
|
|
|
665
835
|
continue;
|
|
666
836
|
if (filter.tabKey != null && tab.key !== filter.tabKey)
|
|
667
837
|
continue;
|
|
668
|
-
for (const section of tab.sections) {
|
|
669
|
-
|
|
838
|
+
for (const [index, section] of tab.sections.entries()) {
|
|
839
|
+
const sectionKey = layoutSectionKey(section);
|
|
840
|
+
if (filter.sectionKey != null && !layoutSectionMatches(section, filter.sectionKey))
|
|
670
841
|
continue;
|
|
842
|
+
const label = sectionKey == null ? `${tab.key}.sections[${index}]` : `${tab.key}.${sectionKey}`;
|
|
671
843
|
containers.push({
|
|
672
844
|
tabKey: tab.key,
|
|
673
|
-
sectionKey
|
|
674
|
-
|
|
675
|
-
|
|
845
|
+
sectionKey,
|
|
846
|
+
section,
|
|
847
|
+
fields: ensureArrayProperty(section, 'fields', `newTabs.${label}.fields`),
|
|
848
|
+
label,
|
|
676
849
|
});
|
|
677
850
|
}
|
|
678
851
|
}
|
|
679
852
|
for (const [tabKey, tabEdit] of Object.entries(this._tabEdits.systemTabEdits ?? {})) {
|
|
680
853
|
if (filter.tabKey != null && tabKey !== filter.tabKey)
|
|
681
854
|
continue;
|
|
682
|
-
for (const section of tabEdit.newSections ?? []) {
|
|
683
|
-
|
|
855
|
+
for (const [index, section] of (tabEdit.newSections ?? []).entries()) {
|
|
856
|
+
const sectionKey = layoutSectionKey(section);
|
|
857
|
+
if (filter.sectionKey != null && !layoutSectionMatches(section, filter.sectionKey))
|
|
684
858
|
continue;
|
|
859
|
+
const label = sectionKey == null ? `${tabKey}.newSections[${index}]` : `${tabKey}.${sectionKey}`;
|
|
685
860
|
containers.push({
|
|
686
861
|
tabKey,
|
|
687
|
-
sectionKey
|
|
688
|
-
|
|
689
|
-
|
|
862
|
+
sectionKey,
|
|
863
|
+
section,
|
|
864
|
+
fields: ensureArrayProperty(section, 'fields', `systemTabEdits.${label}.fields`),
|
|
865
|
+
label,
|
|
690
866
|
});
|
|
691
867
|
}
|
|
692
868
|
}
|
|
@@ -717,6 +893,9 @@ export class CustomObjectPageLayout {
|
|
|
717
893
|
if (tabKey == null) {
|
|
718
894
|
throw new Error('CustomObjectPageLayout addField with systemSection: true requires tabKey.');
|
|
719
895
|
}
|
|
896
|
+
if (sectionKey == null) {
|
|
897
|
+
throw new Error('CustomObjectPageLayout addField with systemSection: true requires sectionKey.');
|
|
898
|
+
}
|
|
720
899
|
const sectionEdit = this.ensureSystemSectionEdit(tabKey, sectionKey);
|
|
721
900
|
if (sectionEdit.deleted === true) {
|
|
722
901
|
throw new Error(`CustomObjectPageLayout system section "${tabKey}.${sectionKey}" is marked deleted.`);
|
|
@@ -755,7 +934,7 @@ export class CustomObjectPageLayout {
|
|
|
755
934
|
assertSectionKeyAvailable(sections, sectionKey, tabKey, allowDuplicate) {
|
|
756
935
|
if (allowDuplicate === true)
|
|
757
936
|
return;
|
|
758
|
-
if (sections.some((section) => section
|
|
937
|
+
if (sectionKey != null && sections.some((section) => layoutSectionMatches(section, sectionKey))) {
|
|
759
938
|
throw new Error(`CustomObjectPageLayout tab "${tabKey}" already contains section "${sectionKey}".`);
|
|
760
939
|
}
|
|
761
940
|
}
|
|
@@ -827,12 +1006,13 @@ export class CustomObjectPageLayout {
|
|
|
827
1006
|
* tabs or sections. Always uses `apiName: 'default'`.
|
|
828
1007
|
*
|
|
829
1008
|
* @param customObject - The custom object to create the layout for.
|
|
830
|
-
* @param props - Optional. Pass `fields` to populate the General section.
|
|
1009
|
+
* @param props - Optional. Pass `section` and `fields` to populate the General section.
|
|
831
1010
|
* @returns A new `CustomObjectPageLayout` registered with the manifest.
|
|
832
1011
|
*
|
|
833
1012
|
* @example
|
|
834
1013
|
* ```ts
|
|
835
1014
|
* CustomObjectPageLayout.basic(memberObj, {
|
|
1015
|
+
* section: profileSection,
|
|
836
1016
|
* fields: [memberFirstName, memberEmail, memberStatusField],
|
|
837
1017
|
* });
|
|
838
1018
|
* ```
|
|
@@ -840,11 +1020,29 @@ export class CustomObjectPageLayout {
|
|
|
840
1020
|
static basic(customObject, props = {}) {
|
|
841
1021
|
const coApiName = customObject.getApiName();
|
|
842
1022
|
const coName = customObject.getName();
|
|
843
|
-
const
|
|
844
|
-
const generalSection =
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1023
|
+
const fields = props.fields ?? [];
|
|
1024
|
+
const generalSection = props.section ??
|
|
1025
|
+
(() => {
|
|
1026
|
+
const alreadySectionedField = fields.find((field) => field.getSectionId() != null);
|
|
1027
|
+
if (alreadySectionedField != null) {
|
|
1028
|
+
throw new Error(`CustomObjectPageLayout.basic field "${alreadySectionedField.getApiName()}" already belongs to ` +
|
|
1029
|
+
`field section "${alreadySectionedField.getSectionId()}". Pass that CustomObjectFieldSection ` +
|
|
1030
|
+
'as section so basic() does not reassign the field.');
|
|
1031
|
+
}
|
|
1032
|
+
const sectionPrefix = coApiName.replace(/__c$/, '').replace(/[^a-zA-Z0-9]+/g, '_');
|
|
1033
|
+
return new CustomObjectFieldSection(customObject, {
|
|
1034
|
+
sectionId: `sec_${sectionPrefix}_general`,
|
|
1035
|
+
name: 'General',
|
|
1036
|
+
});
|
|
1037
|
+
})();
|
|
1038
|
+
assertSectionBelongsTo(customObject, generalSection);
|
|
1039
|
+
for (const field of fields) {
|
|
1040
|
+
const fieldSectionId = field.getSectionId();
|
|
1041
|
+
if (fieldSectionId != null && fieldSectionId !== generalSection.getSectionId()) {
|
|
1042
|
+
throw new Error(`CustomObjectPageLayout.basic field "${field.getApiName()}" belongs to field section ` +
|
|
1043
|
+
`"${fieldSectionId}", but basic() was given section "${generalSection.getSectionId()}".`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
848
1046
|
return new CustomObjectPageLayout(customObject, {
|
|
849
1047
|
apiName: 'default',
|
|
850
1048
|
name: `${coName} Layout`,
|
|
@@ -856,11 +1054,10 @@ export class CustomObjectPageLayout {
|
|
|
856
1054
|
type: 'tab_with_sections',
|
|
857
1055
|
sections: [
|
|
858
1056
|
{
|
|
859
|
-
key: 'general',
|
|
860
1057
|
name: 'General',
|
|
861
1058
|
type: 'fields_section',
|
|
862
1059
|
section: generalSection,
|
|
863
|
-
fields
|
|
1060
|
+
fields,
|
|
864
1061
|
},
|
|
865
1062
|
],
|
|
866
1063
|
},
|