@rippling/rippling-sdk 0.2.0-alpha.49 → 0.2.0-alpha.51

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.
Files changed (72) hide show
  1. package/examples/manifest/existing-manifest-mutations/existing-manifest-mutations.ts +5 -0
  2. package/lib/manifest/custom-object-page-layout.d.mts +167 -48
  3. package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
  4. package/lib/manifest/custom-object-page-layout.d.ts +167 -48
  5. package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
  6. package/lib/manifest/custom-object-page-layout.js +202 -88
  7. package/lib/manifest/custom-object-page-layout.js.map +1 -1
  8. package/lib/manifest/custom-object-page-layout.mjs +202 -88
  9. package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
  10. package/lib/manifest/custom-object.d.mts +1 -1
  11. package/lib/manifest/custom-object.d.ts +1 -1
  12. package/lib/manifest/custom-object.js +1 -1
  13. package/lib/manifest/custom-object.mjs +1 -1
  14. package/lib/manifest/existing-manifest-context.d.mts +1 -0
  15. package/lib/manifest/existing-manifest-context.d.mts.map +1 -1
  16. package/lib/manifest/existing-manifest-context.d.ts +1 -0
  17. package/lib/manifest/existing-manifest-context.d.ts.map +1 -1
  18. package/lib/manifest/existing-manifest-context.js +7 -6
  19. package/lib/manifest/existing-manifest-context.js.map +1 -1
  20. package/lib/manifest/existing-manifest-context.mjs +7 -6
  21. package/lib/manifest/existing-manifest-context.mjs.map +1 -1
  22. package/lib/manifest/field.d.mts +3 -3
  23. package/lib/manifest/field.d.ts +3 -3
  24. package/lib/manifest/field.js +2 -2
  25. package/lib/manifest/field.mjs +2 -2
  26. package/lib/manifest/index.d.mts +1 -1
  27. package/lib/manifest/index.d.mts.map +1 -1
  28. package/lib/manifest/index.d.ts +1 -1
  29. package/lib/manifest/index.d.ts.map +1 -1
  30. package/lib/manifest/index.js.map +1 -1
  31. package/lib/manifest/index.mjs.map +1 -1
  32. package/lib/sdui/components.d.mts +100 -211
  33. package/lib/sdui/components.d.mts.map +1 -1
  34. package/lib/sdui/components.d.ts +100 -211
  35. package/lib/sdui/components.d.ts.map +1 -1
  36. package/lib/sdui/components.js +96 -182
  37. package/lib/sdui/components.js.map +1 -1
  38. package/lib/sdui/components.mjs +93 -178
  39. package/lib/sdui/components.mjs.map +1 -1
  40. package/lib/sdui/index.d.mts +12 -52
  41. package/lib/sdui/index.d.mts.map +1 -1
  42. package/lib/sdui/index.d.ts +12 -52
  43. package/lib/sdui/index.d.ts.map +1 -1
  44. package/lib/sdui/index.js +10 -50
  45. package/lib/sdui/index.js.map +1 -1
  46. package/lib/sdui/index.mjs +5 -25
  47. package/lib/sdui/index.mjs.map +1 -1
  48. package/lib/sdui/propTypes.d.mts +3156 -3515
  49. package/lib/sdui/propTypes.d.mts.map +1 -1
  50. package/lib/sdui/propTypes.d.ts +3156 -3515
  51. package/lib/sdui/propTypes.d.ts.map +1 -1
  52. package/lib/sdui/propTypes.js +1 -1
  53. package/lib/sdui/propTypes.mjs +1 -1
  54. package/package.json +1 -1
  55. package/resources/leave-balances.d.mts +10 -0
  56. package/resources/leave-balances.d.mts.map +1 -1
  57. package/resources/leave-balances.d.ts +10 -0
  58. package/resources/leave-balances.d.ts.map +1 -1
  59. package/src/lib/manifest/custom-object-page-layout.ts +497 -180
  60. package/src/lib/manifest/custom-object.ts +1 -1
  61. package/src/lib/manifest/existing-manifest-context.ts +11 -6
  62. package/src/lib/manifest/field.ts +3 -3
  63. package/src/lib/manifest/index.ts +27 -0
  64. package/src/lib/sdui/components.ts +181 -332
  65. package/src/lib/sdui/index.ts +13 -93
  66. package/src/lib/sdui/propTypes.ts +3509 -3892
  67. package/src/resources/leave-balances.ts +12 -0
  68. package/src/version.ts +1 -1
  69. package/version.d.mts +1 -1
  70. package/version.d.ts +1 -1
  71. package/version.js +1 -1
  72. package/version.mjs +1 -1
@@ -17,32 +17,34 @@ 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');
20
+ const LAYOUT_SECTION_KEY = Symbol('CustomObjectPageLayout.layoutSectionKey');
21
21
  function isCustomObjectFieldSection(value) {
22
22
  return value instanceof custom_object_field_section_1.CustomObjectFieldSection;
23
23
  }
24
- function attachFieldSectionId(section, sectionId) {
25
- if (section.key != null && section.key !== sectionId) {
26
- throw new Error(`CustomObjectPageLayout fields_section key "${section.key}" does not match field section "${sectionId}".`);
27
- }
28
- section.key = sectionId;
29
- Object.defineProperty(section, FIELD_SECTION_ID, {
30
- value: sectionId,
24
+ function explicitLayoutSectionKey(section) {
25
+ const record = section;
26
+ const key = section.key ?? record['layout_section_key'] ?? record['layoutSectionKey'];
27
+ return typeof key === 'string' ? key : undefined;
28
+ }
29
+ function attachLayoutSectionKey(section, sectionKey) {
30
+ if (explicitLayoutSectionKey(section) == null)
31
+ section.key = sectionKey;
32
+ Object.defineProperty(section, LAYOUT_SECTION_KEY, {
33
+ value: sectionKey,
31
34
  enumerable: false,
32
35
  configurable: true,
33
36
  });
34
37
  return section;
35
38
  }
36
39
  function layoutSectionKey(section) {
37
- return (section.key ??
38
- section[FIELD_SECTION_ID]);
40
+ return (explicitLayoutSectionKey(section) ??
41
+ section[LAYOUT_SECTION_KEY]);
39
42
  }
40
43
  function layoutSectionMatches(section, sectionKey) {
41
44
  return layoutSectionKey(section) === sectionKey;
42
45
  }
43
- function inputFieldsSectionHasKey(input) {
44
- const record = input;
45
- return Object.prototype.hasOwnProperty.call(record, 'key') && record['key'] != null;
46
+ function isSerializedFieldsSection(section) {
47
+ return section.type === 'fields_section';
46
48
  }
47
49
  function assertSameObject(customObject, field, sectionKey) {
48
50
  if (field.getCustomObjectApiName() !== customObject.getApiName()) {
@@ -77,10 +79,29 @@ function toSerializedSectionField(fieldRqlName, options = {}) {
77
79
  normalized.hidden = options.hidden;
78
80
  if (options.editable != null)
79
81
  normalized.editable = options.editable;
82
+ if (options.readOnly != null)
83
+ normalized.readOnly = options.readOnly;
84
+ if (options.defaultValue != null)
85
+ normalized.defaultValue = options.defaultValue;
86
+ if (options.height != null)
87
+ normalized.height = options.height;
88
+ if (options.canBeDeleted != null)
89
+ normalized.canBeDeleted = options.canBeDeleted;
80
90
  if (options.movementOption != null)
81
91
  normalized.movementOption = options.movementOption;
82
92
  return normalized;
83
93
  }
94
+ function copySectionOptions(target, input) {
95
+ if (input.canBeDeleted != null)
96
+ target.canBeDeleted = input.canBeDeleted;
97
+ if (input.canBeRenamed != null)
98
+ target.canBeRenamed = input.canBeRenamed;
99
+ if (input.movementOption != null)
100
+ target.movementOption = input.movementOption;
101
+ if (input.hideName != null)
102
+ target.hideName = input.hideName;
103
+ return target;
104
+ }
84
105
  function normalizeSectionField(customObject, section, sectionKey, entry) {
85
106
  if (isField(entry)) {
86
107
  assertSameObject(customObject, entry, sectionKey);
@@ -97,26 +118,28 @@ function normalizeSectionField(customObject, section, sectionKey, entry) {
97
118
  }
98
119
  function normalizeFieldsSection(customObject, input) {
99
120
  assertSectionBelongsTo(customObject, input.section);
100
- const sectionId = input.section.getSectionId();
101
- if (inputFieldsSectionHasKey(input)) {
102
- throw new Error('CustomObjectPageLayout fields_section key is not supported. Omit key.');
103
- }
121
+ const sectionKey = input.key ?? input.section.getSectionId();
104
122
  const name = input.name ?? input.section.getName();
105
- const normalized = {
123
+ const normalized = copySectionOptions({
106
124
  name,
107
125
  type: 'fields_section',
108
- fields: input.fields.map((field) => normalizeSectionField(customObject, input.section, sectionId, field)),
109
- };
126
+ fields: input.fields.map((field) => normalizeSectionField(customObject, input.section, sectionKey, field)),
127
+ }, input);
110
128
  if (input.layout != null)
111
129
  normalized.layout = input.layout;
112
- return attachFieldSectionId(normalized, sectionId);
130
+ return attachLayoutSectionKey(normalized, sectionKey);
131
+ }
132
+ function normalizeSection(customObject, section) {
133
+ if (section.type === 'fields_section')
134
+ return normalizeFieldsSection(customObject, section);
135
+ return { ...section };
113
136
  }
114
137
  function normalizeTab(customObject, tab) {
115
138
  if (tab.type === 'custom_tab')
116
139
  return tab;
117
140
  return {
118
141
  ...tab,
119
- sections: tab.sections.map((section) => normalizeFieldsSection(customObject, section)),
142
+ sections: tab.sections.map((section) => normalizeSection(customObject, section)),
120
143
  };
121
144
  }
122
145
  function normalizeSystemSectionEdit(customObject, tabKey, sectionKey, edit) {
@@ -131,7 +154,7 @@ function normalizeSystemTabEdit(customObject, tabKey, edit) {
131
154
  const { newSections: _newSections, systemSectionEdits: _systemSectionEdits, ...rest } = edit;
132
155
  const normalized = { ...rest };
133
156
  if (edit.newSections != null) {
134
- normalized.newSections = edit.newSections.map((section) => normalizeFieldsSection(customObject, section));
157
+ normalized.newSections = edit.newSections.map((section) => normalizeSection(customObject, section));
135
158
  }
136
159
  if (edit.systemSectionEdits != null) {
137
160
  const systemSectionEdits = {};
@@ -189,6 +212,18 @@ function normalizeHeaderEdits(headerEdits) {
189
212
  for (const [index, field] of (headerEdits.newFields ?? []).entries()) {
190
213
  assertKnownKeys(field, NEW_HEADER_FIELD_KEYS, `CustomObjectPageLayout headerEdits.newFields[${index}]`, 'Use { rqlName, key } entries, or call addHeaderField(field).');
191
214
  }
215
+ if (headerEdits.newTitleField === '') {
216
+ throw new Error('CustomObjectPageLayout headerEdits.newTitleField cannot be empty. Use titleFieldDeleted.');
217
+ }
218
+ if (headerEdits.newDescriptionField === '') {
219
+ throw new Error('CustomObjectPageLayout headerEdits.newDescriptionField cannot be empty. Use descriptionFieldDeleted.');
220
+ }
221
+ if (headerEdits.newTitleField != null && headerEdits.titleFieldDeleted === true) {
222
+ throw new Error('CustomObjectPageLayout headerEdits cannot both set newTitleField and titleFieldDeleted.');
223
+ }
224
+ if (headerEdits.newDescriptionField != null && headerEdits.descriptionFieldDeleted === true) {
225
+ throw new Error('CustomObjectPageLayout headerEdits cannot both set newDescriptionField and descriptionFieldDeleted.');
226
+ }
192
227
  if (headerEdits.headerFieldsOrder == null)
193
228
  return headerEdits;
194
229
  return {
@@ -251,52 +286,90 @@ function fieldRqlNameOf(entry) {
251
286
  if (entry == null || typeof entry !== 'object')
252
287
  return undefined;
253
288
  const record = entry;
254
- const fieldRqlName = record['fieldRqlName'] ?? record['field_rql_name'];
289
+ const fieldRqlName = record['fieldRqlName'] ?? record['field_rql_name'] ?? record['apiName'] ?? record['api_name'];
255
290
  return typeof fieldRqlName === 'string' ? fieldRqlName : undefined;
256
291
  }
257
- function explicitFieldSectionId(entry) {
258
- if (entry == null || typeof entry !== 'object')
259
- return undefined;
260
- const record = entry;
261
- const fieldSectionId = record['fieldSection'] ?? record['field_section'];
262
- return typeof fieldSectionId === 'string' ? fieldSectionId : undefined;
292
+ function addFieldSectionLayoutSection(index, fieldSectionId, entry) {
293
+ const entries = index.get(fieldSectionId) ?? [];
294
+ if (!entries.some((existing) => existing.tabKey === entry.tabKey && existing.sectionKey === entry.sectionKey)) {
295
+ entries.push(entry);
296
+ }
297
+ index.set(fieldSectionId, entries);
263
298
  }
264
- function inferredSectionIdFromFields(section, resolveFieldSectionId) {
265
- const sectionIds = new Set();
299
+ function indexFieldsSectionInput(index, tabKey, section) {
300
+ addFieldSectionLayoutSection(index, section.section.getSectionId(), {
301
+ tabKey,
302
+ sectionKey: section.key ?? section.section.getSectionId(),
303
+ });
304
+ }
305
+ function indexPageLayoutSectionInput(index, tabKey, section) {
306
+ if (section.type === 'fields_section')
307
+ indexFieldsSectionInput(index, tabKey, section);
308
+ }
309
+ function buildFieldSectionLayoutIndexFromProps(tabEdits) {
310
+ const index = new Map();
311
+ if (tabEdits == null)
312
+ return index;
313
+ for (const tab of tabEdits.newTabs ?? []) {
314
+ if (tab.type !== 'tab_with_sections')
315
+ continue;
316
+ for (const section of tab.sections)
317
+ indexPageLayoutSectionInput(index, tab.key, section);
318
+ }
319
+ for (const [tabKey, tabEdit] of Object.entries(tabEdits.systemTabEdits ?? {})) {
320
+ for (const section of tabEdit.newSections ?? []) {
321
+ indexPageLayoutSectionInput(index, tabKey, section);
322
+ }
323
+ }
324
+ return index;
325
+ }
326
+ function indexLoadedFieldsSection(index, tabKey, section, resolveFieldSectionId) {
327
+ const sectionKey = layoutSectionKey(section);
328
+ if (sectionKey == null || resolveFieldSectionId == null)
329
+ return;
266
330
  for (const field of section.fields ?? []) {
267
- const explicitSectionId = explicitFieldSectionId(field);
268
- if (explicitSectionId != null) {
269
- sectionIds.add(explicitSectionId);
331
+ const fieldRqlName = fieldRqlNameOf(field);
332
+ if (fieldRqlName == null)
270
333
  continue;
334
+ const fieldSectionId = resolveFieldSectionId(fieldRqlName);
335
+ if (fieldSectionId != null) {
336
+ addFieldSectionLayoutSection(index, fieldSectionId, { tabKey, sectionKey });
271
337
  }
272
- const fieldApiName = fieldRqlNameOf(field);
273
- if (fieldApiName == null || resolveFieldSectionId == null)
338
+ }
339
+ }
340
+ function buildFieldSectionLayoutIndexFromLoadedTabEdits(tabEdits, resolveFieldSectionId) {
341
+ const index = new Map();
342
+ for (const tab of tabEdits.newTabs ?? []) {
343
+ if (!isTabWithSections(tab))
274
344
  continue;
275
- const resolvedSectionId = resolveFieldSectionId(fieldApiName);
276
- if (resolvedSectionId != null)
277
- sectionIds.add(resolvedSectionId);
345
+ for (const section of tab.sections) {
346
+ if (isSerializedFieldsSection(section)) {
347
+ indexLoadedFieldsSection(index, tab.key, section, resolveFieldSectionId);
348
+ }
349
+ }
278
350
  }
279
- return sectionIds.size === 1 ? [...sectionIds][0] : undefined;
351
+ for (const [tabKey, tabEdit] of Object.entries(tabEdits.systemTabEdits ?? {})) {
352
+ for (const section of tabEdit.newSections ?? []) {
353
+ if (isSerializedFieldsSection(section)) {
354
+ indexLoadedFieldsSection(index, tabKey, section, resolveFieldSectionId);
355
+ }
356
+ }
357
+ }
358
+ return index;
280
359
  }
281
- function annotateLoadedFieldsSection(section, resolveFieldSectionId) {
282
- const explicitSectionId = layoutSectionKey(section);
283
- const inferredSectionId = inferredSectionIdFromFields(section, resolveFieldSectionId);
284
- if (explicitSectionId != null && inferredSectionId != null && explicitSectionId !== inferredSectionId) {
285
- throw new Error(`CustomObjectPageLayout fields_section key "${explicitSectionId}" does not match ` +
286
- `field section "${inferredSectionId}".`);
287
- }
288
- const sectionId = explicitSectionId ?? inferredSectionId;
289
- return sectionId == null ? section : attachFieldSectionId(section, sectionId);
360
+ function annotateLoadedFieldsSection(section) {
361
+ const sectionKey = layoutSectionKey(section);
362
+ return sectionKey == null ? section : attachLayoutSectionKey(section, sectionKey);
290
363
  }
291
- function annotateLoadedTabEdits(tabEdits, resolveFieldSectionId) {
364
+ function annotateLoadedTabEdits(tabEdits) {
292
365
  for (const tab of tabEdits.newTabs ?? []) {
293
366
  if (!isTabWithSections(tab))
294
367
  continue;
295
- tab.sections = tab.sections.map((section) => annotateLoadedFieldsSection(section, resolveFieldSectionId));
368
+ tab.sections = tab.sections.map((section) => isSerializedFieldsSection(section) ? annotateLoadedFieldsSection(section) : section);
296
369
  }
297
370
  for (const tabEdit of Object.values(tabEdits.systemTabEdits ?? {})) {
298
371
  if (tabEdit.newSections != null) {
299
- tabEdit.newSections = tabEdit.newSections.map((section) => annotateLoadedFieldsSection(section, resolveFieldSectionId));
372
+ tabEdit.newSections = tabEdit.newSections.map((section) => isSerializedFieldsSection(section) ? annotateLoadedFieldsSection(section) : section);
300
373
  }
301
374
  }
302
375
  return tabEdits;
@@ -412,6 +485,7 @@ class CustomObjectPageLayout {
412
485
  this._customObjectApiName = customObject.getApiName();
413
486
  this._name = props.name;
414
487
  this._tabEdits = normalizeTabEdits(customObject, props.tabEdits ?? {});
488
+ this._fieldSectionLayoutSections = buildFieldSectionLayoutIndexFromProps(props.tabEdits);
415
489
  this._headerEdits = normalizeHeaderEdits(props.headerEdits ?? {});
416
490
  this._visibilityConditions = props.visibilityConditions ?? {};
417
491
  this._blueprintKey = props.blueprintKey;
@@ -430,15 +504,17 @@ class CustomObjectPageLayout {
430
504
  /** @internal Hydrates a page layout from existing manifest wire JSON. */
431
505
  static _fromExistingComponent(customObject, component, resolveFieldSectionId) {
432
506
  const layout = Object.create(CustomObjectPageLayout.prototype);
507
+ const tabEdits = annotateLoadedTabEdits(cloneJson(component['tab_edits'] ?? {}));
433
508
  Object.assign(layout, {
434
509
  _layoutId: (0, _existing_component_fields_1.requireStringComponentField)(component, 'layout_id', CustomObjectPageLayout.componentType),
435
510
  _apiName: (0, _existing_component_fields_1.requireStringComponentField)(component, 'api_name', CustomObjectPageLayout.componentType),
436
511
  _customObjectApiName: (0, _existing_component_fields_1.requireStringComponentField)(component, 'object_rql_name', CustomObjectPageLayout.componentType),
437
512
  _name: (0, _existing_component_fields_1.requireStringComponentField)(component, 'name', CustomObjectPageLayout.componentType),
438
- _tabEdits: annotateLoadedTabEdits(cloneJson(component['tab_edits'] ?? {}), resolveFieldSectionId),
513
+ _tabEdits: tabEdits,
439
514
  _headerEdits: cloneJson(component['header_edits'] ?? {}),
440
515
  _visibilityConditions: cloneJson(component['visibility_conditions'] ?? {}),
441
516
  _blueprintKey: component['blueprint_key'],
517
+ _fieldSectionLayoutSections: buildFieldSectionLayoutIndexFromLoadedTabEdits(tabEdits, resolveFieldSectionId),
442
518
  });
443
519
  customObject._register(layout);
444
520
  return layout;
@@ -474,8 +550,8 @@ class CustomObjectPageLayout {
474
550
  * Adds multiple fields to an existing layout section.
475
551
  *
476
552
  * Fields are inserted in the order provided. For custom layout sections,
477
- * pass `options.section` so TypeScript and runtime validation agree on the
478
- * target `CUSTOM_OBJECT_FIELD_SECTION`.
553
+ * pass `section` to choose the typed field section. Use `sectionKey` only
554
+ * when a field section maps to multiple layout sections.
479
555
  */
480
556
  addFields(fields, options) {
481
557
  if (fields.length === 0)
@@ -489,14 +565,19 @@ class CustomObjectPageLayout {
489
565
  return this.ensureSystemSectionFieldContainer(options.tabKey, options.sectionKey);
490
566
  })()
491
567
  : (() => {
492
- assertOptionAbsent(options, 'sectionKey', 'CustomObjectPageLayout addField sectionKey is only supported with systemSection: true. ' +
493
- 'Pass section: CustomObjectFieldSection for custom layout sections.');
494
568
  assertOptionAbsent(options, 'fieldSection', 'CustomObjectPageLayout addField fieldSection is no longer supported. ' +
495
569
  'Pass section: CustomObjectFieldSection instead.');
496
- const section = requireFieldSectionOption(options.section, 'CustomObjectPageLayout addField section');
497
- this.assertFieldSectionBelongsToLayout(section);
498
- fieldSectionForMutation = section;
499
- return this.requireOneFieldContainer(options.tabKey, section.getSectionId(), section);
570
+ const section = options.section == null ?
571
+ undefined
572
+ : requireFieldSectionOption(options.section, 'CustomObjectPageLayout addField section');
573
+ if (section != null) {
574
+ this.assertFieldSectionBelongsToLayout(section);
575
+ fieldSectionForMutation = section;
576
+ }
577
+ const sectionKey = options.sectionKey ??
578
+ (section == null ? undefined : this.layoutSectionKeyForFieldSection(options.tabKey, section)) ??
579
+ section?.getSectionId();
580
+ return this.requireOneFieldContainer(options.tabKey, sectionKey, section);
500
581
  })();
501
582
  const entries = fields.map((field) => this.serializeFieldForMutation(field, container.sectionKey ?? container.label, options, fieldSectionForMutation));
502
583
  if (options.allowDuplicate !== true) {
@@ -530,12 +611,12 @@ class CustomObjectPageLayout {
530
611
  sectionKey = options.sectionKey;
531
612
  }
532
613
  else {
533
- assertOptionAbsent(options, 'sectionKey', 'CustomObjectPageLayout removeField sectionKey is only supported with systemSection: true. ' +
534
- 'Pass section: CustomObjectFieldSection for custom layout sections.');
614
+ sectionKey = options.sectionKey;
535
615
  if (options.section != null) {
536
616
  const section = requireFieldSectionOption(options.section, 'CustomObjectPageLayout removeField section');
537
617
  this.assertFieldSectionBelongsToLayout(section);
538
- sectionKey = section.getSectionId();
618
+ sectionKey ?? (sectionKey = this.layoutSectionKeyForFieldSection(options.tabKey, section));
619
+ sectionKey ?? (sectionKey = section.getSectionId());
539
620
  }
540
621
  }
541
622
  const containers = this.findFieldContainers({
@@ -593,6 +674,9 @@ class CustomObjectPageLayout {
593
674
  const sectionsOrder = this.ensureSystemSectionsOrder(tabEdit);
594
675
  insertAt(sections, normalized, options.position);
595
676
  insertUniqueAt(sectionsOrder, layoutSectionKey(normalized), options.position);
677
+ if (section.type === 'fields_section') {
678
+ this.rememberFieldSectionLayoutSection(section.section, options.tabKey, layoutSectionKey(normalized));
679
+ }
596
680
  return this;
597
681
  }
598
682
  const tab = this.findNewTabWithSections(options.tabKey);
@@ -602,6 +686,9 @@ class CustomObjectPageLayout {
602
686
  }
603
687
  this.assertSectionKeyAvailable(tab.sections, layoutSectionKey(normalized), options.tabKey, options.allowDuplicate);
604
688
  insertAt(tab.sections, normalized, options.position);
689
+ if (section.type === 'fields_section') {
690
+ this.rememberFieldSectionLayoutSection(section.section, tab.key, layoutSectionKey(normalized));
691
+ }
605
692
  return this;
606
693
  }
607
694
  removeSection(sectionOrKey, options = {}) {
@@ -618,9 +705,15 @@ class CustomObjectPageLayout {
618
705
  this.removeSectionVisibility(sectionOrKey);
619
706
  return this;
620
707
  }
621
- const section = requireFieldSectionOption(sectionOrKey, 'CustomObjectPageLayout removeSection section');
622
- this.assertFieldSectionBelongsToLayout(section);
623
- const sectionKey = section.getSectionId();
708
+ let sectionKey;
709
+ if (typeof sectionOrKey === 'string') {
710
+ sectionKey = sectionOrKey;
711
+ }
712
+ else {
713
+ const section = requireFieldSectionOption(sectionOrKey, 'CustomObjectPageLayout removeSection section');
714
+ this.assertFieldSectionBelongsToLayout(section);
715
+ sectionKey = this.layoutSectionKeyForFieldSection(options.tabKey, section) ?? section.getSectionId();
716
+ }
624
717
  const matches = [];
625
718
  for (const tab of this._tabEdits.newTabs ?? []) {
626
719
  if (!isTabWithSections(tab))
@@ -628,7 +721,7 @@ class CustomObjectPageLayout {
628
721
  if (options.tabKey != null && tab.key !== options.tabKey)
629
722
  continue;
630
723
  tab.sections.forEach((section, index) => {
631
- if (layoutSectionMatches(section, sectionKey))
724
+ if (isSerializedFieldsSection(section) && layoutSectionMatches(section, sectionKey))
632
725
  matches.push({ sections: tab.sections, section, index, tabKey: tab.key });
633
726
  });
634
727
  }
@@ -636,7 +729,7 @@ class CustomObjectPageLayout {
636
729
  if (options.tabKey != null && tabKey !== options.tabKey)
637
730
  continue;
638
731
  (tabEdit.newSections ?? []).forEach((section, index) => {
639
- if (layoutSectionMatches(section, sectionKey))
732
+ if (isSerializedFieldsSection(section) && layoutSectionMatches(section, sectionKey))
640
733
  matches.push({ sections: tabEdit.newSections, section, index, tabEdit, tabKey });
641
734
  });
642
735
  }
@@ -674,6 +767,10 @@ class CustomObjectPageLayout {
674
767
  const tabsOrder = this.ensureTabsOrder();
675
768
  insertAt(newTabs, normalized, options.position);
676
769
  insertUniqueAt(tabsOrder, normalized.key, options.position);
770
+ if (tab.type === 'tab_with_sections') {
771
+ for (const section of tab.sections)
772
+ indexPageLayoutSectionInput(this._fieldSectionLayoutSections, tab.key, section);
773
+ }
677
774
  return this;
678
775
  }
679
776
  /**
@@ -732,13 +829,11 @@ class CustomObjectPageLayout {
732
829
  this.removeHeaderOrderFieldIfUnused(removedField);
733
830
  return this;
734
831
  }
735
- /** Sets the record title field in `header_edits`. */
736
832
  setTitleField(field) {
737
833
  const previousTitleField = this._headerEdits.newTitleField;
738
834
  const nextTitleField = this.headerFieldApiName(field);
739
835
  this._headerEdits.newTitleField = nextTitleField;
740
- if (field != null)
741
- this._headerEdits.titleFieldDeleted = false;
836
+ this._headerEdits.titleFieldDeleted = false;
742
837
  insertUniqueAt(this.ensureHeaderFieldsOrder(), nextTitleField, 'end');
743
838
  this.removeHeaderOrderFieldIfUnused(previousTitleField);
744
839
  return this;
@@ -751,13 +846,11 @@ class CustomObjectPageLayout {
751
846
  this.removeHeaderOrderFieldIfUnused(previousTitleField);
752
847
  return this;
753
848
  }
754
- /** Sets the record description field in `header_edits`. */
755
849
  setDescriptionField(field) {
756
850
  const previousDescriptionField = this._headerEdits.newDescriptionField;
757
851
  const nextDescriptionField = this.headerFieldApiName(field);
758
852
  this._headerEdits.newDescriptionField = nextDescriptionField;
759
- if (field != null)
760
- this._headerEdits.descriptionFieldDeleted = false;
853
+ this._headerEdits.descriptionFieldDeleted = false;
761
854
  insertUniqueAt(this.ensureHeaderFieldsOrder(), nextDescriptionField, 'end');
762
855
  this.removeHeaderOrderFieldIfUnused(previousDescriptionField);
763
856
  return this;
@@ -833,20 +926,19 @@ class CustomObjectPageLayout {
833
926
  'pass a Field instance or { field }.');
834
927
  }
835
928
  normalizeSectionForMutation(input) {
929
+ if (input.type !== 'fields_section')
930
+ return { ...input };
836
931
  this.assertFieldSectionBelongsToLayout(input.section);
837
- const sectionId = input.section.getSectionId();
838
- if (inputFieldsSectionHasKey(input)) {
839
- throw new Error('CustomObjectPageLayout fields_section key is not supported. Omit key.');
840
- }
932
+ const sectionKey = input.key ?? input.section.getSectionId();
841
933
  const name = input.name ?? input.section.getName();
842
- const normalized = {
934
+ const normalized = copySectionOptions({
843
935
  name,
844
936
  type: 'fields_section',
845
- fields: input.fields.map((field) => this.normalizeSectionFieldForMutation(input.section, sectionId, field)),
846
- };
937
+ fields: input.fields.map((field) => this.normalizeSectionFieldForMutation(input.section, sectionKey, field)),
938
+ }, input);
847
939
  if (input.layout != null)
848
940
  normalized.layout = input.layout;
849
- return attachFieldSectionId(normalized, sectionId);
941
+ return attachLayoutSectionKey(normalized, sectionKey);
850
942
  }
851
943
  normalizeTabForMutation(tab) {
852
944
  if (tab.type === 'custom_tab')
@@ -888,6 +980,26 @@ class CustomObjectPageLayout {
888
980
  `"${section.getModelApiName()}", but the layout belongs to "${this._customObjectApiName}".`);
889
981
  }
890
982
  }
983
+ rememberFieldSectionLayoutSection(section, tabKey, sectionKey) {
984
+ if (sectionKey == null)
985
+ return;
986
+ addFieldSectionLayoutSection(this._fieldSectionLayoutSections, section.getSectionId(), {
987
+ tabKey,
988
+ sectionKey,
989
+ });
990
+ }
991
+ layoutSectionKeyForFieldSection(tabKey, section) {
992
+ const entries = this._fieldSectionLayoutSections.get(section.getSectionId()) ?? [];
993
+ const matches = tabKey == null ? entries : entries.filter((entry) => entry.tabKey === tabKey);
994
+ const sectionKeys = [...new Set(matches.map((entry) => entry.sectionKey))];
995
+ if (sectionKeys.length === 0)
996
+ return undefined;
997
+ if (sectionKeys.length === 1)
998
+ return sectionKeys[0];
999
+ const scope = tabKey == null ? '' : ` in tab "${tabKey}"`;
1000
+ throw new Error(`CustomObjectPageLayout field section "${section.getSectionId()}" maps to multiple layout sections${scope}: ` +
1001
+ `${sectionKeys.join(', ')}. Pass sectionKey to choose one.`);
1002
+ }
891
1003
  requireOneFieldContainer(tabKey, sectionKey, fallbackFieldSection) {
892
1004
  const containers = this.findFieldContainers({
893
1005
  tabKey,
@@ -907,8 +1019,8 @@ class CustomObjectPageLayout {
907
1019
  `"${fallbackFieldSection.getModelApiName()}", but the layout belongs to "${this._customObjectApiName}".`);
908
1020
  }
909
1021
  const container = unkeyedContainers[0];
910
- attachFieldSectionId(container.section, fallbackFieldSection.getSectionId());
911
- container.sectionKey = fallbackFieldSection.getSectionId();
1022
+ attachLayoutSectionKey(container.section, sectionKey);
1023
+ container.sectionKey = sectionKey;
912
1024
  container.label = `${container.tabKey}.${container.sectionKey}`;
913
1025
  return container;
914
1026
  }
@@ -935,6 +1047,8 @@ class CustomObjectPageLayout {
935
1047
  if (filter.tabKey != null && tab.key !== filter.tabKey)
936
1048
  continue;
937
1049
  for (const [index, section] of tab.sections.entries()) {
1050
+ if (!isSerializedFieldsSection(section))
1051
+ continue;
938
1052
  const sectionKey = layoutSectionKey(section);
939
1053
  if (filter.sectionKey != null && !layoutSectionMatches(section, filter.sectionKey))
940
1054
  continue;
@@ -952,6 +1066,8 @@ class CustomObjectPageLayout {
952
1066
  if (filter.tabKey != null && tabKey !== filter.tabKey)
953
1067
  continue;
954
1068
  for (const [index, section] of (tabEdit.newSections ?? []).entries()) {
1069
+ if (!isSerializedFieldsSection(section))
1070
+ continue;
955
1071
  const sectionKey = layoutSectionKey(section);
956
1072
  if (filter.sectionKey != null && !layoutSectionMatches(section, filter.sectionKey))
957
1073
  continue;
@@ -1060,8 +1176,6 @@ class CustomObjectPageLayout {
1060
1176
  return normalized;
1061
1177
  }
1062
1178
  headerFieldApiName(field) {
1063
- if (field == null)
1064
- return null;
1065
1179
  if (typeof field === 'string')
1066
1180
  return field;
1067
1181
  this.assertFieldBelongsToLayout(field);