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