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

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 (57) hide show
  1. package/lib/manifest/custom-object-page-layout.d.mts +148 -34
  2. package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
  3. package/lib/manifest/custom-object-page-layout.d.ts +148 -34
  4. package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
  5. package/lib/manifest/custom-object-page-layout.js +61 -18
  6. package/lib/manifest/custom-object-page-layout.js.map +1 -1
  7. package/lib/manifest/custom-object-page-layout.mjs +61 -18
  8. package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
  9. package/lib/manifest/custom-object.d.mts +1 -1
  10. package/lib/manifest/custom-object.d.ts +1 -1
  11. package/lib/manifest/custom-object.js +1 -1
  12. package/lib/manifest/custom-object.mjs +1 -1
  13. package/lib/manifest/field.d.mts +3 -3
  14. package/lib/manifest/field.d.ts +3 -3
  15. package/lib/manifest/field.js +2 -2
  16. package/lib/manifest/field.mjs +2 -2
  17. package/lib/manifest/index.d.mts +1 -1
  18. package/lib/manifest/index.d.mts.map +1 -1
  19. package/lib/manifest/index.d.ts +1 -1
  20. package/lib/manifest/index.d.ts.map +1 -1
  21. package/lib/manifest/index.js.map +1 -1
  22. package/lib/manifest/index.mjs.map +1 -1
  23. package/lib/sdui/components.d.mts +100 -211
  24. package/lib/sdui/components.d.mts.map +1 -1
  25. package/lib/sdui/components.d.ts +100 -211
  26. package/lib/sdui/components.d.ts.map +1 -1
  27. package/lib/sdui/components.js +96 -182
  28. package/lib/sdui/components.js.map +1 -1
  29. package/lib/sdui/components.mjs +93 -178
  30. package/lib/sdui/components.mjs.map +1 -1
  31. package/lib/sdui/index.d.mts +12 -52
  32. package/lib/sdui/index.d.mts.map +1 -1
  33. package/lib/sdui/index.d.ts +12 -52
  34. package/lib/sdui/index.d.ts.map +1 -1
  35. package/lib/sdui/index.js +10 -50
  36. package/lib/sdui/index.js.map +1 -1
  37. package/lib/sdui/index.mjs +5 -25
  38. package/lib/sdui/index.mjs.map +1 -1
  39. package/lib/sdui/propTypes.d.mts +3156 -3515
  40. package/lib/sdui/propTypes.d.mts.map +1 -1
  41. package/lib/sdui/propTypes.d.ts +3156 -3515
  42. package/lib/sdui/propTypes.d.ts.map +1 -1
  43. package/lib/sdui/propTypes.js +1 -1
  44. package/lib/sdui/propTypes.mjs +1 -1
  45. package/package.json +1 -1
  46. package/src/lib/manifest/custom-object-page-layout.ts +294 -79
  47. package/src/lib/manifest/custom-object.ts +1 -1
  48. package/src/lib/manifest/field.ts +3 -3
  49. package/src/lib/manifest/index.ts +27 -0
  50. package/src/lib/sdui/components.ts +181 -332
  51. package/src/lib/sdui/index.ts +13 -93
  52. package/src/lib/sdui/propTypes.ts +3509 -3892
  53. package/src/version.ts +1 -1
  54. package/version.d.mts +1 -1
  55. package/version.d.ts +1 -1
  56. package/version.js +1 -1
  57. package/version.mjs +1 -1
@@ -11,6 +11,23 @@ export interface PageLayoutDeletionIdentifier {
11
11
  objectRqlName: string;
12
12
  }
13
13
 
14
+ export type ObjectRqlName = string;
15
+ export type BlueprintKey = string;
16
+ export type LayoutApiName = string;
17
+ export type TabKey = string;
18
+ export type SectionKey = string;
19
+ export type HeaderFieldKey = string;
20
+ export type FieldRqlName = string;
21
+ export type RelatedObjectRqlName = string;
22
+ export type CanvasCompositionId = string;
23
+ export type HeaderButtonKey = string;
24
+
25
+ export type JsonPrimitive = string | number | boolean | null;
26
+ export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
27
+ export interface JsonObject {
28
+ [key: string]: JsonValue;
29
+ }
30
+
14
31
  // ---------------------------------------------------------------------------
15
32
  // header_edits — HeaderEditsDTO
16
33
  // ---------------------------------------------------------------------------
@@ -18,7 +35,7 @@ export interface PageLayoutDeletionIdentifier {
18
35
  /** Edit to a built-in (system) header field. */
19
36
  export interface SystemHeaderFieldEdit {
20
37
  /** Replace the system field's RQL field reference. Optional. */
21
- newRqlField?: string;
38
+ newRqlField?: FieldRqlName;
22
39
  /** Remove this system field from the header. Optional. */
23
40
  deleted?: boolean;
24
41
  }
@@ -31,9 +48,9 @@ export interface SystemHeaderFieldEdit {
31
48
  */
32
49
  export interface NewHeaderField {
33
50
  /** RQL field name to display. Required. */
34
- rqlName: string;
51
+ rqlName: FieldRqlName;
35
52
  /** Stable key for this field. Required. */
36
- key: string;
53
+ key: HeaderFieldKey;
37
54
  /** Whether the user can remove this field. Optional. */
38
55
  canBeDeleted?: boolean;
39
56
  /** Whether the user can swap this field for another. Optional. */
@@ -53,48 +70,93 @@ export interface NewHeaderField {
53
70
  */
54
71
  export interface HeaderEdits {
55
72
  /** Replace the record title field (pass the field's api_name). Optional. */
56
- newTitleField?: string | null;
73
+ newTitleField?: FieldRqlName;
57
74
  /** Remove the title field from the header. Optional. */
58
75
  titleFieldDeleted?: boolean;
59
76
  /** Replace the record description field. Optional. */
60
- newDescriptionField?: string | null;
77
+ newDescriptionField?: FieldRqlName;
61
78
  /** Remove the description field from the header. Optional. */
62
79
  descriptionFieldDeleted?: boolean;
63
80
  /** Edits to built-in system header fields, keyed by system field key. Optional. */
64
- systemFieldEdits?: Record<string, SystemHeaderFieldEdit>;
81
+ systemFieldEdits?: Record<HeaderFieldKey, SystemHeaderFieldEdit>;
65
82
  /** Additional custom fields to add to the header. Optional. */
66
83
  newFields?: NewHeaderField[];
67
84
  /** Ordered list of header field RQL names. Custom header keys are accepted and converted. Optional. */
68
- headerFieldsOrder?: string[];
85
+ headerFieldsOrder?: FieldRqlName[];
69
86
  /** Action button keys to show in the header. Optional. */
70
- buttons?: string[];
87
+ buttons?: HeaderButtonKey[];
88
+ }
89
+
90
+ interface SerializedSystemHeaderFieldEdit extends Omit<SystemHeaderFieldEdit, 'newRqlField'> {
91
+ newRqlField?: FieldRqlName | null;
92
+ }
93
+
94
+ interface SerializedHeaderEdits
95
+ extends Omit<HeaderEdits, 'newTitleField' | 'newDescriptionField' | 'systemFieldEdits'> {
96
+ newTitleField?: FieldRqlName | null;
97
+ newDescriptionField?: FieldRqlName | null;
98
+ systemFieldEdits?: Record<HeaderFieldKey, SerializedSystemHeaderFieldEdit>;
71
99
  }
72
100
 
73
101
  // ---------------------------------------------------------------------------
74
102
  // tab_edits — TabEditsDTO + section/tab DTOs
75
103
  // ---------------------------------------------------------------------------
76
104
 
105
+ export type SectionFieldsLayout = 'one_column' | 'two_column' | 'three_column' | 'responsive';
106
+ export type TabLayout = 'vertical' | 'horizontal';
107
+ export type SectionMovementOption = 'static' | 'tab' | 'free';
108
+ export type FieldMovementOption = 'tab' | 'free';
109
+ export type CustomObjectCustomSectionComponentKey = 'attachments' | 'comments';
110
+
111
+ /** Default value wrapper used by `SectionField.defaultValue`. */
112
+ export interface SectionFieldDefaultValue {
113
+ value: JsonValue;
114
+ }
115
+
77
116
  /** Options for one field entry inside a `fields_section`. */
78
117
  export interface SectionFieldOptions {
79
118
  /** Hide this field from view. Optional. */
80
119
  hidden?: boolean;
81
120
  /** Allow inline editing of this field. Optional. */
82
121
  editable?: boolean;
122
+ /** Render this field read-only. Optional. */
123
+ readOnly?: boolean;
124
+ /** Default value metadata for the field. Optional. */
125
+ defaultValue?: SectionFieldDefaultValue;
126
+ /** Rich-text field height. Optional. */
127
+ height?: number;
128
+ /** Whether the layout manager can remove this field. Optional. */
129
+ canBeDeleted?: boolean;
83
130
  /** Controls how the field can be moved in the UI. Optional. */
84
- movementOption?: 'tab' | 'free';
131
+ movementOption?: FieldMovementOption;
85
132
  }
86
133
 
87
134
  /** A typed field entry inside a `fields_section`. */
88
- export interface SectionFieldRef extends SectionFieldOptions {
135
+ export interface SectionField extends SectionFieldOptions {
89
136
  /** Field to display. Required. */
90
137
  field: Field;
91
138
  }
92
139
 
140
+ /** @deprecated Use `SectionField` for object entries or `SectionFieldInput` for field arrays. */
141
+ export type SectionFieldRef = SectionField;
142
+
93
143
  /** One field entry inside a `fields_section`. */
94
- export type SectionField = Field | SectionFieldRef;
144
+ export type SectionFieldInput = Field | SectionField;
145
+
146
+ /** Common edit controls for sections. */
147
+ export interface PageLayoutSectionOptions {
148
+ /** Whether the layout manager can delete this section. Optional. */
149
+ canBeDeleted?: boolean;
150
+ /** Whether the layout manager can rename this section. Optional. */
151
+ canBeRenamed?: boolean;
152
+ /** Controls how the section can be moved. Optional. */
153
+ movementOption?: SectionMovementOption;
154
+ /** Hide the section title. Optional. */
155
+ hideName?: boolean;
156
+ }
95
157
 
96
158
  /** A standard field-grid section inside a tab. */
97
- export interface FieldsSection {
159
+ export interface FieldsSection extends PageLayoutSectionOptions {
98
160
  type: 'fields_section';
99
161
  /** Field section metadata for the fields displayed in this layout section. Required. */
100
162
  section: CustomObjectFieldSection;
@@ -103,24 +165,105 @@ export interface FieldsSection {
103
165
  /** Display name of the layout section. Optional. Defaults to the field section name. */
104
166
  name?: string;
105
167
  /** Fields to display in this section. Required. */
106
- fields: SectionField[];
168
+ fields: SectionFieldInput[];
107
169
  /** Column layout for the fields. Optional. @default `'responsive'` (backend default). */
108
170
  layout?: SectionFieldsLayout;
109
171
  }
110
172
 
111
- /**
112
- * A section inside a page layout tab.
113
- *
114
- * Only `fields_section` is modeled today. Add other section types
115
- * (`related_object_section`, `custom_section`, …) explicitly as needed.
116
- */
117
- export type PageLayoutSection = FieldsSection;
173
+ export interface RelatedObjectSection extends PageLayoutSectionOptions {
174
+ type: 'related_object_section';
175
+ /** Stable layout section key. Required. */
176
+ key: SectionKey;
177
+ /** Display name of the layout section. Required. */
178
+ name: string;
179
+ /** Related object whose records point back to this custom object. Required. */
180
+ relatedObjectRqlName: RelatedObjectRqlName;
181
+ /** Field on the related object that points back to this custom object. Required. */
182
+ relatedFieldRqlName: FieldRqlName;
183
+ /** Field RQL names to display for related records. Required. */
184
+ fields: FieldRqlName[];
185
+ }
186
+
187
+ export interface RelatedFieldSection extends PageLayoutSectionOptions {
188
+ type: 'related_field_section';
189
+ /** Stable layout section key. Required. */
190
+ key: SectionKey;
191
+ /** Display name of the layout section. Required. */
192
+ name: string;
193
+ /** Related object reached from this custom object. Required. */
194
+ relatedObjectRqlName: RelatedObjectRqlName;
195
+ /** Field on this custom object that points to the related object. Required. */
196
+ relatedFieldRqlName: FieldRqlName;
197
+ /** Field RQL names to display from the related object. Required. */
198
+ fields: FieldRqlName[];
199
+ /** Column layout for the fields. Optional. @default `'responsive'` (backend default). */
200
+ layout?: SectionFieldsLayout;
201
+ }
202
+
203
+ export interface CustomSection extends PageLayoutSectionOptions {
204
+ type: 'custom_section';
205
+ /** Stable layout section key. Required. */
206
+ key: SectionKey;
207
+ /** Display name of the layout section. Required. */
208
+ name: string;
209
+ /** Custom object section component key. Required. */
210
+ componentKey: CustomObjectCustomSectionComponentKey;
211
+ }
212
+
213
+ export interface ResizableLayoutPosition {
214
+ identifier: string;
215
+ width: number;
216
+ height: number;
217
+ minWidth?: number;
218
+ minHeight?: number;
219
+ maxWidth?: number;
220
+ maxHeight?: number;
221
+ }
222
+
223
+ export interface ReportsSectionItem {
224
+ key: string;
225
+ reportId: string;
226
+ position: ResizableLayoutPosition;
227
+ contextualFieldAttrName?: FieldRqlName;
228
+ visualizationType?: string;
229
+ }
230
+
231
+ export interface ReportsSection extends PageLayoutSectionOptions {
232
+ type: 'reports_section';
233
+ /** Stable layout section key. Required. */
234
+ key: SectionKey;
235
+ /** Display name of the layout section. Required. */
236
+ name: string;
237
+ /** Reports to display in this section. Required. */
238
+ reports: ReportsSectionItem[];
239
+ }
240
+
241
+ export interface CanvasSection extends PageLayoutSectionOptions {
242
+ type: 'canvas_section';
243
+ /** Stable layout section key. Required. */
244
+ key: SectionKey;
245
+ /** Display name of the layout section. Required. */
246
+ name: string;
247
+ /** Canvas composition to render. Required. */
248
+ canvasCompositionId: CanvasCompositionId;
249
+ /** Composition input name to field or state path mapping. Optional. */
250
+ inputMapping?: Record<string, string>;
251
+ }
252
+
253
+ /** A section inside a page layout tab. */
254
+ export type PageLayoutSection =
255
+ | FieldsSection
256
+ | RelatedObjectSection
257
+ | RelatedFieldSection
258
+ | CustomSection
259
+ | ReportsSection
260
+ | CanvasSection;
118
261
 
119
262
  /** A tab that contains one or more field-grid sections. */
120
263
  export interface TabWithSections {
121
264
  type: 'tab_with_sections';
122
265
  /** Stable key for this tab. Required. */
123
- key: string;
266
+ key: TabKey;
124
267
  /** Display name of the tab. Required. */
125
268
  name: string;
126
269
  /** Sections inside this tab. Required. */
@@ -131,7 +274,7 @@ export interface TabWithSections {
131
274
  export interface CustomTab {
132
275
  type: 'custom_tab';
133
276
  /** Stable key for this tab. Required. */
134
- key: string;
277
+ key: TabKey;
135
278
  /** Display name of the tab. Required. */
136
279
  name: string;
137
280
  /** Key of the custom component to render. Required. */
@@ -142,8 +285,6 @@ export interface CustomTab {
142
285
  export type PageLayoutTab = TabWithSections | CustomTab;
143
286
 
144
287
  /** Column layout for a `fields_section`. */
145
- export type SectionFieldsLayout = 'one_column' | 'two_column' | 'three_column' | 'responsive';
146
-
147
288
  /** Edits to a built-in section inside a system tab. */
148
289
  export interface SystemSectionEdit {
149
290
  /** Rename this section. Optional. */
@@ -153,7 +294,7 @@ export interface SystemSectionEdit {
153
294
  /** Change the column layout. Optional. */
154
295
  newLayout?: SectionFieldsLayout;
155
296
  /** Add fields to this section. Optional. */
156
- newFields?: SectionField[];
297
+ newFields?: SectionFieldInput[];
157
298
  /** Hide the section name header. Optional. */
158
299
  newHideName?: boolean;
159
300
  }
@@ -167,9 +308,9 @@ export interface SystemTabEdit {
167
308
  /** Add new sections to this tab. Optional. */
168
309
  newSections?: PageLayoutSection[];
169
310
  /** Edits to built-in sections inside this tab, keyed by section key. Optional. */
170
- systemSectionEdits?: Record<string, SystemSectionEdit>;
311
+ systemSectionEdits?: Record<SectionKey, SystemSectionEdit>;
171
312
  /** Reorder sections by key. Optional. */
172
- sectionsOrder?: string[];
313
+ sectionsOrder?: SectionKey[];
173
314
  }
174
315
 
175
316
  /**
@@ -184,11 +325,11 @@ export interface TabEdits {
184
325
  /** New tabs to add to this layout. Each must include a `type` discriminator. Optional. */
185
326
  newTabs?: PageLayoutTab[];
186
327
  /** Edits to existing system tabs, keyed by tab key. Optional. */
187
- systemTabEdits?: Record<string, SystemTabEdit>;
328
+ systemTabEdits?: Record<TabKey, SystemTabEdit>;
188
329
  /** Ordered list of tab `key` values (new + system). Optional. */
189
- tabsOrder?: string[];
330
+ tabsOrder?: TabKey[];
190
331
  /** Tab strip orientation. Optional. */
191
- newTabLayout?: 'vertical' | 'horizontal';
332
+ newTabLayout?: TabLayout;
192
333
  }
193
334
 
194
335
  // ---------------------------------------------------------------------------
@@ -205,7 +346,7 @@ export interface VisibilityCondition {
205
346
  /** RQL formula that controls visibility. Required. */
206
347
  rqlFormula: string;
207
348
  /** Layered conditions tree (AST). Required. */
208
- layeredConditionsTree: Record<string, any>;
349
+ layeredConditionsTree: JsonObject;
209
350
  }
210
351
 
211
352
  /**
@@ -215,9 +356,9 @@ export interface VisibilityCondition {
215
356
  */
216
357
  export interface VisibilityConditions {
217
358
  /** Per-section visibility conditions, keyed by section key. Optional. */
218
- sections?: Record<string, VisibilityCondition>;
359
+ sections?: Record<SectionKey, VisibilityCondition>;
219
360
  /** Per-tab visibility conditions, keyed by tab key. Optional. */
220
- tabs?: Record<string, VisibilityCondition>;
361
+ tabs?: Record<TabKey, VisibilityCondition>;
221
362
  }
222
363
 
223
364
  // ---------------------------------------------------------------------------
@@ -455,7 +596,7 @@ interface SerializedSectionField extends SectionFieldOptions {
455
596
  fieldRqlName: string;
456
597
  }
457
598
 
458
- interface SerializedFieldsSection {
599
+ interface SerializedFieldsSection extends PageLayoutSectionOptions {
459
600
  key?: string;
460
601
  type: 'fields_section';
461
602
  name: string;
@@ -463,11 +604,19 @@ interface SerializedFieldsSection {
463
604
  layout?: SectionFieldsLayout;
464
605
  }
465
606
 
607
+ type SerializedPageLayoutSection =
608
+ | SerializedFieldsSection
609
+ | RelatedObjectSection
610
+ | RelatedFieldSection
611
+ | CustomSection
612
+ | ReportsSection
613
+ | CanvasSection;
614
+
466
615
  interface SerializedTabWithSections {
467
616
  type: 'tab_with_sections';
468
617
  key: string;
469
618
  name: string;
470
- sections: SerializedFieldsSection[];
619
+ sections: SerializedPageLayoutSection[];
471
620
  }
472
621
 
473
622
  type SerializedPageLayoutTab = SerializedTabWithSections | CustomTab;
@@ -477,7 +626,7 @@ interface SerializedSystemSectionEdit extends Omit<SystemSectionEdit, 'newFields
477
626
  }
478
627
 
479
628
  interface SerializedSystemTabEdit extends Omit<SystemTabEdit, 'newSections' | 'systemSectionEdits'> {
480
- newSections?: SerializedFieldsSection[];
629
+ newSections?: SerializedPageLayoutSection[];
481
630
  systemSectionEdits?: Record<string, SerializedSystemSectionEdit>;
482
631
  }
483
632
 
@@ -502,7 +651,7 @@ function isField(value: unknown): value is Field {
502
651
  );
503
652
  }
504
653
 
505
- function isFieldRef(value: SectionField): value is SectionFieldRef {
654
+ function isFieldRef(value: SectionFieldInput): value is SectionField {
506
655
  const candidate = value as { field?: unknown } | null;
507
656
  return candidate != null && typeof candidate === 'object' && isField(candidate.field);
508
657
  }
@@ -528,22 +677,26 @@ function attachFieldSectionId(section: SerializedFieldsSection, sectionId: strin
528
677
  return section;
529
678
  }
530
679
 
531
- function layoutSectionKey(section: SerializedFieldsSection): string | undefined {
680
+ function layoutSectionKey(section: { key?: string }): string | undefined {
532
681
  return (
533
682
  section.key ??
534
683
  (section as unknown as Record<typeof FIELD_SECTION_ID, string | undefined>)[FIELD_SECTION_ID]
535
684
  );
536
685
  }
537
686
 
538
- function layoutSectionMatches(section: SerializedFieldsSection, sectionKey: string): boolean {
687
+ function layoutSectionMatches(section: { key?: string }, sectionKey: string): boolean {
539
688
  return layoutSectionKey(section) === sectionKey;
540
689
  }
541
690
 
542
- function inputFieldsSectionHasKey(input: PageLayoutSection): boolean {
691
+ function inputFieldsSectionHasKey(input: FieldsSection): boolean {
543
692
  const record = input as unknown as Record<string, unknown>;
544
693
  return Object.prototype.hasOwnProperty.call(record, 'key') && record['key'] != null;
545
694
  }
546
695
 
696
+ function isSerializedFieldsSection(section: SerializedPageLayoutSection): section is SerializedFieldsSection {
697
+ return section.type === 'fields_section';
698
+ }
699
+
547
700
  function assertSameObject(customObject: CustomObject, field: Field, sectionKey: string): void {
548
701
  if (field.getCustomObjectApiName() !== customObject.getApiName()) {
549
702
  throw new Error(
@@ -585,15 +738,30 @@ function toSerializedSectionField(
585
738
  const normalized: SerializedSectionField = { fieldRqlName };
586
739
  if (options.hidden != null) normalized.hidden = options.hidden;
587
740
  if (options.editable != null) normalized.editable = options.editable;
741
+ if (options.readOnly != null) normalized.readOnly = options.readOnly;
742
+ if (options.defaultValue != null) normalized.defaultValue = options.defaultValue;
743
+ if (options.height != null) normalized.height = options.height;
744
+ if (options.canBeDeleted != null) normalized.canBeDeleted = options.canBeDeleted;
588
745
  if (options.movementOption != null) normalized.movementOption = options.movementOption;
589
746
  return normalized;
590
747
  }
591
748
 
749
+ function copySectionOptions<T extends PageLayoutSectionOptions>(
750
+ target: T,
751
+ input: PageLayoutSectionOptions,
752
+ ): T {
753
+ if (input.canBeDeleted != null) target.canBeDeleted = input.canBeDeleted;
754
+ if (input.canBeRenamed != null) target.canBeRenamed = input.canBeRenamed;
755
+ if (input.movementOption != null) target.movementOption = input.movementOption;
756
+ if (input.hideName != null) target.hideName = input.hideName;
757
+ return target;
758
+ }
759
+
592
760
  function normalizeSectionField(
593
761
  customObject: CustomObject,
594
762
  section: CustomObjectFieldSection | string,
595
763
  sectionKey: string,
596
- entry: SectionField,
764
+ entry: SectionFieldInput,
597
765
  ): SerializedSectionField {
598
766
  if (isField(entry)) {
599
767
  assertSameObject(customObject, entry, sectionKey);
@@ -613,30 +781,40 @@ function normalizeSectionField(
613
781
  );
614
782
  }
615
783
 
616
- function normalizeFieldsSection(
617
- customObject: CustomObject,
618
- input: PageLayoutSection,
619
- ): SerializedFieldsSection {
784
+ function normalizeFieldsSection(customObject: CustomObject, input: FieldsSection): SerializedFieldsSection {
620
785
  assertSectionBelongsTo(customObject, input.section);
621
786
  const sectionId = input.section.getSectionId();
622
787
  if (inputFieldsSectionHasKey(input)) {
623
788
  throw new Error('CustomObjectPageLayout fields_section key is not supported. Omit key.');
624
789
  }
625
790
  const name = input.name ?? input.section.getName();
626
- const normalized: SerializedFieldsSection = {
627
- name,
628
- type: 'fields_section',
629
- fields: input.fields.map((field) => normalizeSectionField(customObject, input.section, sectionId, field)),
630
- };
791
+ const normalized = copySectionOptions<SerializedFieldsSection>(
792
+ {
793
+ name,
794
+ type: 'fields_section',
795
+ fields: input.fields.map((field) =>
796
+ normalizeSectionField(customObject, input.section, sectionId, field),
797
+ ),
798
+ },
799
+ input,
800
+ );
631
801
  if (input.layout != null) normalized.layout = input.layout;
632
802
  return attachFieldSectionId(normalized, sectionId);
633
803
  }
634
804
 
805
+ function normalizeSection(
806
+ customObject: CustomObject,
807
+ section: PageLayoutSection,
808
+ ): SerializedPageLayoutSection {
809
+ if (section.type === 'fields_section') return normalizeFieldsSection(customObject, section);
810
+ return { ...section };
811
+ }
812
+
635
813
  function normalizeTab(customObject: CustomObject, tab: PageLayoutTab): SerializedPageLayoutTab {
636
814
  if (tab.type === 'custom_tab') return tab;
637
815
  return {
638
816
  ...tab,
639
- sections: tab.sections.map((section) => normalizeFieldsSection(customObject, section)),
817
+ sections: tab.sections.map((section) => normalizeSection(customObject, section)),
640
818
  };
641
819
  }
642
820
 
@@ -664,7 +842,7 @@ function normalizeSystemTabEdit(
664
842
  const { newSections: _newSections, systemSectionEdits: _systemSectionEdits, ...rest } = edit;
665
843
  const normalized: SerializedSystemTabEdit = { ...rest };
666
844
  if (edit.newSections != null) {
667
- normalized.newSections = edit.newSections.map((section) => normalizeFieldsSection(customObject, section));
845
+ normalized.newSections = edit.newSections.map((section) => normalizeSection(customObject, section));
668
846
  }
669
847
  if (edit.systemSectionEdits != null) {
670
848
  const systemSectionEdits: Record<string, SerializedSystemSectionEdit> = {};
@@ -730,7 +908,7 @@ function assertKnownKeys(
730
908
  }
731
909
  }
732
910
 
733
- function normalizeHeaderEdits(headerEdits: HeaderEdits): HeaderEdits {
911
+ function normalizeHeaderEdits(headerEdits: HeaderEdits): SerializedHeaderEdits {
734
912
  assertKnownKeys(
735
913
  headerEdits as unknown as Record<string, unknown>,
736
914
  HEADER_EDIT_KEYS,
@@ -756,6 +934,27 @@ function normalizeHeaderEdits(headerEdits: HeaderEdits): HeaderEdits {
756
934
  );
757
935
  }
758
936
 
937
+ if (headerEdits.newTitleField === '') {
938
+ throw new Error(
939
+ 'CustomObjectPageLayout headerEdits.newTitleField cannot be empty. Use titleFieldDeleted.',
940
+ );
941
+ }
942
+ if (headerEdits.newDescriptionField === '') {
943
+ throw new Error(
944
+ 'CustomObjectPageLayout headerEdits.newDescriptionField cannot be empty. Use descriptionFieldDeleted.',
945
+ );
946
+ }
947
+ if (headerEdits.newTitleField != null && headerEdits.titleFieldDeleted === true) {
948
+ throw new Error(
949
+ 'CustomObjectPageLayout headerEdits cannot both set newTitleField and titleFieldDeleted.',
950
+ );
951
+ }
952
+ if (headerEdits.newDescriptionField != null && headerEdits.descriptionFieldDeleted === true) {
953
+ throw new Error(
954
+ 'CustomObjectPageLayout headerEdits cannot both set newDescriptionField and descriptionFieldDeleted.',
955
+ );
956
+ }
957
+
759
958
  if (headerEdits.headerFieldsOrder == null) return headerEdits;
760
959
  return {
761
960
  ...headerEdits,
@@ -768,13 +967,13 @@ function addUniqueHeaderOrderField(order: string[], field: unknown): void {
768
967
  if (!order.includes(field)) order.push(field);
769
968
  }
770
969
 
771
- function normalizeHeaderFieldsOrder(headerEdits: HeaderEdits): string[] {
970
+ function normalizeHeaderFieldsOrder(headerEdits: SerializedHeaderEdits): string[] {
772
971
  const customFieldRqlByKey = new Map(
773
972
  (headerEdits.newFields ?? []).map((field) => [field.key, field.rqlName]),
774
973
  );
775
974
  const systemFieldRqlByKey = new Map(
776
975
  Object.entries(headerEdits.systemFieldEdits ?? {})
777
- .filter((entry): entry is [string, SystemHeaderFieldEdit & { newRqlField: string }] => {
976
+ .filter((entry): entry is [string, SerializedSystemHeaderFieldEdit & { newRqlField: string }] => {
778
977
  const [, edit] = entry;
779
978
  return typeof edit.newRqlField === 'string' && edit.newRqlField.length > 0;
780
979
  })
@@ -894,13 +1093,19 @@ function annotateLoadedTabEdits(
894
1093
  ): SerializedTabEdits {
895
1094
  for (const tab of tabEdits.newTabs ?? []) {
896
1095
  if (!isTabWithSections(tab)) continue;
897
- tab.sections = tab.sections.map((section) => annotateLoadedFieldsSection(section, resolveFieldSectionId));
1096
+ tab.sections = tab.sections.map((section) =>
1097
+ isSerializedFieldsSection(section) ?
1098
+ annotateLoadedFieldsSection(section, resolveFieldSectionId)
1099
+ : section,
1100
+ );
898
1101
  }
899
1102
 
900
1103
  for (const tabEdit of Object.values(tabEdits.systemTabEdits ?? {})) {
901
1104
  if (tabEdit.newSections != null) {
902
1105
  tabEdit.newSections = tabEdit.newSections.map((section) =>
903
- annotateLoadedFieldsSection(section, resolveFieldSectionId),
1106
+ isSerializedFieldsSection(section) ?
1107
+ annotateLoadedFieldsSection(section, resolveFieldSectionId)
1108
+ : section,
904
1109
  );
905
1110
  }
906
1111
  }
@@ -1020,7 +1225,7 @@ export class CustomObjectPageLayout {
1020
1225
  private readonly _customObjectApiName: string;
1021
1226
  private readonly _name: string;
1022
1227
  private readonly _tabEdits: SerializedTabEdits;
1023
- private readonly _headerEdits: HeaderEdits;
1228
+ private readonly _headerEdits: SerializedHeaderEdits;
1024
1229
  private readonly _visibilityConditions: VisibilityConditions;
1025
1230
  private readonly _blueprintKey: string | undefined;
1026
1231
 
@@ -1280,7 +1485,7 @@ export class CustomObjectPageLayout {
1280
1485
  const normalized = this.normalizeSectionForMutation(section);
1281
1486
  if (options.systemTab === true) {
1282
1487
  const tabEdit = this.ensureSystemTabEdit(options.tabKey);
1283
- const sections = ensureArrayProperty<SerializedFieldsSection>(
1488
+ const sections = ensureArrayProperty<SerializedPageLayoutSection>(
1284
1489
  tabEdit as Record<string, any>,
1285
1490
  'newSections',
1286
1491
  `systemTabEdits.${options.tabKey}.newSections`,
@@ -1346,7 +1551,7 @@ export class CustomObjectPageLayout {
1346
1551
  this.assertFieldSectionBelongsToLayout(section);
1347
1552
  const sectionKey = section.getSectionId();
1348
1553
  const matches: Array<{
1349
- sections: SerializedFieldsSection[];
1554
+ sections: SerializedPageLayoutSection[];
1350
1555
  section: SerializedFieldsSection;
1351
1556
  index: number;
1352
1557
  tabEdit?: SerializedSystemTabEdit;
@@ -1356,14 +1561,14 @@ export class CustomObjectPageLayout {
1356
1561
  if (!isTabWithSections(tab)) continue;
1357
1562
  if (options.tabKey != null && tab.key !== options.tabKey) continue;
1358
1563
  tab.sections.forEach((section, index) => {
1359
- if (layoutSectionMatches(section, sectionKey))
1564
+ if (isSerializedFieldsSection(section) && layoutSectionMatches(section, sectionKey))
1360
1565
  matches.push({ sections: tab.sections, section, index, tabKey: tab.key });
1361
1566
  });
1362
1567
  }
1363
1568
  for (const [tabKey, tabEdit] of Object.entries(this._tabEdits.systemTabEdits ?? {})) {
1364
1569
  if (options.tabKey != null && tabKey !== options.tabKey) continue;
1365
1570
  (tabEdit.newSections ?? []).forEach((section, index) => {
1366
- if (layoutSectionMatches(section, sectionKey))
1571
+ if (isSerializedFieldsSection(section) && layoutSectionMatches(section, sectionKey))
1367
1572
  matches.push({ sections: tabEdit.newSections!, section, index, tabEdit, tabKey });
1368
1573
  });
1369
1574
  }
@@ -1380,7 +1585,7 @@ export class CustomObjectPageLayout {
1380
1585
  }
1381
1586
 
1382
1587
  const removals = removeAll ? matches : matches.slice(0, 1);
1383
- const byArray = new Map<SerializedFieldsSection[], number[]>();
1588
+ const byArray = new Map<SerializedPageLayoutSection[], number[]>();
1384
1589
  for (const match of removals) {
1385
1590
  const indexes = byArray.get(match.sections) ?? [];
1386
1591
  indexes.push(match.index);
@@ -1474,11 +1679,13 @@ export class CustomObjectPageLayout {
1474
1679
  }
1475
1680
 
1476
1681
  /** Sets the record title field in `header_edits`. */
1477
- setTitleField(field: Field | string | null): this {
1682
+ setTitleField(field: Field): this;
1683
+ setTitleField(fieldApiName: FieldRqlName): this;
1684
+ setTitleField(field: Field | FieldRqlName): this {
1478
1685
  const previousTitleField = this._headerEdits.newTitleField;
1479
1686
  const nextTitleField = this.headerFieldApiName(field);
1480
1687
  this._headerEdits.newTitleField = nextTitleField;
1481
- if (field != null) this._headerEdits.titleFieldDeleted = false;
1688
+ this._headerEdits.titleFieldDeleted = false;
1482
1689
  insertUniqueAt(this.ensureHeaderFieldsOrder(), nextTitleField, 'end');
1483
1690
  this.removeHeaderOrderFieldIfUnused(previousTitleField);
1484
1691
  return this;
@@ -1494,11 +1701,13 @@ export class CustomObjectPageLayout {
1494
1701
  }
1495
1702
 
1496
1703
  /** Sets the record description field in `header_edits`. */
1497
- setDescriptionField(field: Field | string | null): this {
1704
+ setDescriptionField(field: Field): this;
1705
+ setDescriptionField(fieldApiName: FieldRqlName): this;
1706
+ setDescriptionField(field: Field | FieldRqlName): this {
1498
1707
  const previousDescriptionField = this._headerEdits.newDescriptionField;
1499
1708
  const nextDescriptionField = this.headerFieldApiName(field);
1500
1709
  this._headerEdits.newDescriptionField = nextDescriptionField;
1501
- if (field != null) this._headerEdits.descriptionFieldDeleted = false;
1710
+ this._headerEdits.descriptionFieldDeleted = false;
1502
1711
  insertUniqueAt(this.ensureHeaderFieldsOrder(), nextDescriptionField, 'end');
1503
1712
  this.removeHeaderOrderFieldIfUnused(previousDescriptionField);
1504
1713
  return this;
@@ -1571,7 +1780,7 @@ export class CustomObjectPageLayout {
1571
1780
  private normalizeSectionFieldForMutation(
1572
1781
  section: CustomObjectFieldSection | string,
1573
1782
  sectionKey: string,
1574
- entry: SectionField,
1783
+ entry: SectionFieldInput,
1575
1784
  ): SerializedSectionField {
1576
1785
  if (isField(entry)) {
1577
1786
  this.assertFieldBelongsToLayout(entry);
@@ -1591,7 +1800,9 @@ export class CustomObjectPageLayout {
1591
1800
  );
1592
1801
  }
1593
1802
 
1594
- private normalizeSectionForMutation(input: PageLayoutSection): SerializedFieldsSection {
1803
+ private normalizeSectionForMutation(input: PageLayoutSection): SerializedPageLayoutSection {
1804
+ if (input.type !== 'fields_section') return { ...input };
1805
+
1595
1806
  this.assertFieldSectionBelongsToLayout(input.section);
1596
1807
 
1597
1808
  const sectionId = input.section.getSectionId();
@@ -1599,13 +1810,16 @@ export class CustomObjectPageLayout {
1599
1810
  throw new Error('CustomObjectPageLayout fields_section key is not supported. Omit key.');
1600
1811
  }
1601
1812
  const name = input.name ?? input.section.getName();
1602
- const normalized: SerializedFieldsSection = {
1603
- name,
1604
- type: 'fields_section',
1605
- fields: input.fields.map((field) =>
1606
- this.normalizeSectionFieldForMutation(input.section, sectionId, field),
1607
- ),
1608
- };
1813
+ const normalized = copySectionOptions<SerializedFieldsSection>(
1814
+ {
1815
+ name,
1816
+ type: 'fields_section',
1817
+ fields: input.fields.map((field) =>
1818
+ this.normalizeSectionFieldForMutation(input.section, sectionId, field),
1819
+ ),
1820
+ },
1821
+ input,
1822
+ );
1609
1823
  if (input.layout != null) normalized.layout = input.layout;
1610
1824
  return attachFieldSectionId(normalized, sectionId);
1611
1825
  }
@@ -1738,6 +1952,7 @@ export class CustomObjectPageLayout {
1738
1952
  if (!isTabWithSections(tab)) continue;
1739
1953
  if (filter.tabKey != null && tab.key !== filter.tabKey) continue;
1740
1954
  for (const [index, section] of tab.sections.entries()) {
1955
+ if (!isSerializedFieldsSection(section)) continue;
1741
1956
  const sectionKey = layoutSectionKey(section);
1742
1957
  if (filter.sectionKey != null && !layoutSectionMatches(section, filter.sectionKey)) continue;
1743
1958
  const label = sectionKey == null ? `${tab.key}.sections[${index}]` : `${tab.key}.${sectionKey}`;
@@ -1758,6 +1973,7 @@ export class CustomObjectPageLayout {
1758
1973
  for (const [tabKey, tabEdit] of Object.entries(this._tabEdits.systemTabEdits ?? {})) {
1759
1974
  if (filter.tabKey != null && tabKey !== filter.tabKey) continue;
1760
1975
  for (const [index, section] of (tabEdit.newSections ?? []).entries()) {
1976
+ if (!isSerializedFieldsSection(section)) continue;
1761
1977
  const sectionKey = layoutSectionKey(section);
1762
1978
  if (filter.sectionKey != null && !layoutSectionMatches(section, filter.sectionKey)) continue;
1763
1979
  const label = sectionKey == null ? `${tabKey}.newSections[${index}]` : `${tabKey}.${sectionKey}`;
@@ -1870,7 +2086,7 @@ export class CustomObjectPageLayout {
1870
2086
  }
1871
2087
 
1872
2088
  private assertSectionKeyAvailable(
1873
- sections: SerializedFieldsSection[],
2089
+ sections: SerializedPageLayoutSection[],
1874
2090
  sectionKey: string | undefined,
1875
2091
  tabKey: string,
1876
2092
  allowDuplicate: boolean | undefined,
@@ -1903,8 +2119,7 @@ export class CustomObjectPageLayout {
1903
2119
  return normalized;
1904
2120
  }
1905
2121
 
1906
- private headerFieldApiName(field: Field | string | null): string | null {
1907
- if (field == null) return null;
2122
+ private headerFieldApiName(field: Field | FieldRqlName): FieldRqlName {
1908
2123
  if (typeof field === 'string') return field;
1909
2124
  this.assertFieldBelongsToLayout(field);
1910
2125
  return field.getApiName();